<?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: eleonorarocchi</title>
    <description>The latest articles on DEV Community by eleonorarocchi (@eleonorarocchi).</description>
    <link>https://dev.to/eleonorarocchi</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%2F886966%2Fe7384b4e-0bff-4739-bd9a-a796b6ef7110.png</url>
      <title>DEV Community: eleonorarocchi</title>
      <link>https://dev.to/eleonorarocchi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eleonorarocchi"/>
    <language>en</language>
    <item>
      <title>Setting Up a Local AI Coding Agent with Ollama and Aider (part 3)</title>
      <dc:creator>eleonorarocchi</dc:creator>
      <pubDate>Fri, 17 Jul 2026 20:14:00 +0000</pubDate>
      <link>https://dev.to/eleonorarocchi/setting-up-a-local-ai-coding-agent-with-ollama-and-aider-part-3-23gk</link>
      <guid>https://dev.to/eleonorarocchi/setting-up-a-local-ai-coding-agent-with-ollama-and-aider-part-3-23gk</guid>
      <description>&lt;h2&gt;
  
  
  Dashboard
&lt;/h2&gt;

&lt;p&gt;After collecting the metrics in &lt;code&gt;ollama_usage.jsonl&lt;/code&gt;, I wanted a way to visualize them.&lt;/p&gt;

&lt;p&gt;I used two tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Streamlit → quick, operational view
Grafana   → more stable dashboards and long-term monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both read the same data, but in different ways.&lt;/p&gt;

&lt;p&gt;The final architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Aider → proxy on 11435 → ollama_usage.jsonl
                            ├─ Streamlit
                            └─ exporter → Prometheus → Grafana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Streamlit
&lt;/h2&gt;

&lt;p&gt;Streamlit was the fastest way to build a local user interface.&lt;/p&gt;

&lt;p&gt;Inside WSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv ~/.venvs/aider-dashboard
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.venvs/aider-dashboard/bin/activate
pip &lt;span class="nb"&gt;install &lt;/span&gt;streamlit pandas streamlit-autorefresh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I created the dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/aider_usage_dashboard.py &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import json
from pathlib import Path

import pandas as pd
import streamlit as st
from streamlit_autorefresh import st_autorefresh

LOG_PATH = Path.home() / "ollama_usage.jsonl"

st.set_page_config(
    page_title="Aider / Ollama usage dashboard",
    layout="wide",
)

st_autorefresh(interval=5000, key="aider_dashboard_refresh")

st.title("Aider / Ollama usage dashboard")
st.caption("Local statistics: tokens, response times, and generation speed.")

if not LOG_PATH.exists():
    st.warning(f"No file found: {LOG_PATH}")
    st.stop()

rows = []
with LOG_PATH.open("r", encoding="utf-8") as f:
    for line in f:
        line = line.strip()
        if not line:
            continue
        try:
            rows.append(json.loads(line))
        except json.JSONDecodeError:
            pass

if not rows:
    st.warning("The file exists, but it does not contain any valid metrics yet.")
    st.stop()

df = pd.DataFrame(rows)

if "ts" in df.columns:
    df["ts"] = pd.to_datetime(df["ts"], errors="coerce")
    df = df.sort_values("ts")
    df["run"] = range(1, len(df) + 1)
else:
    df["run"] = range(1, len(df) + 1)

numeric_cols = [
    "prompt_tokens",
    "response_tokens",
    "total_tokens",
    "wall_time_s",
    "ollama_total_s",
    "load_s",
    "prompt_eval_s",
    "generation_s",
    "generation_tokens_per_s",
]

for col in numeric_cols:
    if col in df.columns:
        df[col] = pd.to_numeric(df[col], errors="coerce")

total_calls = len(df)
total_tokens = int(df["total_tokens"].sum()) if "total_tokens" in df.columns else 0
avg_time = df["wall_time_s"].mean() if "wall_time_s" in df.columns else None
avg_tps = df["generation_tokens_per_s"].mean() if "generation_tokens_per_s" in df.columns else None

c1, c2, c3, c4 = st.columns(4)

c1.metric("Recorded responses", total_calls)
c2.metric("Total tokens", f"{total_tokens:,}")
c3.metric("Average response time", f"{avg_time:.1f}s" if pd.notna(avg_time) else "n/a")
c4.metric("Average tokens/s", f"{avg_tps:.2f}" if pd.notna(avg_tps) else "n/a")

st.divider()

left, right = st.columns(2)

with left:
    st.subheader("Tokens per response")
    token_cols = [c for c in ["prompt_tokens", "response_tokens", "total_tokens"] if c in df.columns]
    if token_cols:
        st.line_chart(df.set_index("run")[token_cols])

with right:
    st.subheader("Response times")
    time_cols = [c for c in ["wall_time_s", "ollama_total_s", "generation_s", "prompt_eval_s"] if c in df.columns]
    if time_cols:
        st.line_chart(df.set_index("run")[time_cols])

left, right = st.columns(2)

with left:
    st.subheader("Generation speed")
    if "generation_tokens_per_s" in df.columns:
        st.line_chart(df.set_index("run")[["generation_tokens_per_s"]])

with right:
    st.subheader("Input vs output tokens")
    cols = [c for c in ["prompt_tokens", "response_tokens"] if c in df.columns]
    if cols:
        st.bar_chart(df.set_index("run")[cols])

st.divider()

st.subheader("Latest responses")
show_cols = [
    c for c in [
        "ts",
        "model",
        "prompt_tokens",
        "response_tokens",
        "total_tokens",
        "wall_time_s",
        "ollama_total_s",
        "generation_s",
        "generation_tokens_per_s",
        "done_reason",
    ]
    if c in df.columns
]

st.dataframe(
    df[show_cols].tail(50).sort_index(ascending=False),
    use_container_width=True,
)

st.caption(f"Reading data from: {LOG_PATH}")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To start it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.venvs/aider-dashboard/bin/activate
streamlit run ~/aider_usage_dashboard.py &lt;span class="nt"&gt;--server&lt;/span&gt;.address 127.0.0.1 &lt;span class="nt"&gt;--server&lt;/span&gt;.port 8501
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8501
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used this as a quick operational dashboard. Of course, this was only the starting point; I later customized it with all the metrics I actually needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grafana
&lt;/h2&gt;

&lt;p&gt;Grafana, a widely used data visualization and analytics tool, requires a few additional components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exporter → Prometheus → Grafana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker Desktop was already installed on my Windows PC, but I first had to enable WSL integration from Docker Desktop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Settings → Resources → WSL Integration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, inside WSL, I fixed the Docker permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;groupadd docker 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
newgrp docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To verify the setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker version
docker compose version
docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Monitoring Directory
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/monitoring/llm-grafana
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/monitoring/llm-grafana

&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; prometheus
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; grafana/provisioning/datasources
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ollama-jsonl-exporter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Prometheus Exporter
&lt;/h2&gt;

&lt;p&gt;The exporter reads the JSONL file and exposes Prometheus metrics on port &lt;code&gt;9108&lt;/code&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="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ollama-jsonl-exporter/Dockerfile &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;DOCKER&lt;/span&gt;&lt;span class="sh"&gt;'
FROM python:3.12-slim
WORKDIR /app
COPY exporter.py /app/exporter.py
ENV LOG_PATH=/data/ollama_usage.jsonl
ENV PORT=9108
EXPOSE 9108
CMD ["python", "/app/exporter.py"]
&lt;/span&gt;&lt;span class="no"&gt;DOCKER
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;exporter.py&lt;/code&gt; file is the component that converts the JSONL data into Prometheus metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prometheus
&lt;/h2&gt;

&lt;p&gt;Prometheus is the monitoring engine. It is responsible for collecting and storing metrics such as CPU usage, memory consumption, and application-specific measurements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; prometheus/prometheus.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="sh"&gt;'
global:
  scrape_interval: 5s
  evaluation_interval: 5s

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["prometheus:9090"]

  - job_name: "aider-ollama"
    static_configs:
      - targets: ["ollama-jsonl-exporter:9108"]
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Grafana Data Source
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; grafana/provisioning/datasources/prometheus.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="sh"&gt;'
apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Docker Compose
&lt;/h2&gt;

&lt;p&gt;The working configuration mounts the entire &lt;code&gt;prometheus&lt;/code&gt; directory rather than the individual configuration file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; docker-compose.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="sh"&gt;'
services:
  ollama-jsonl-exporter:
    build:
      context: ./ollama-jsonl-exporter
    container_name: ollama-jsonl-exporter
    restart: unless-stopped
    volumes:
      - &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;/ollama_usage.jsonl:/data/ollama_usage.jsonl:ro
    ports:
      - "127.0.0.1:9108:9108"

  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus-llm
    restart: unless-stopped
    depends_on:
      - ollama-jsonl-exporter
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--storage.tsdb.path=/prometheus"
      - "--storage.tsdb.retention.time=30d"
    volumes:
      - ./prometheus:/etc/prometheus:ro
      - prometheus_data:/prometheus
    ports:
      - "127.0.0.1:9090:9090"

  grafana:
    image: grafana/grafana:latest
    container_name: grafana-llm
    restart: unless-stopped
    depends_on:
      - prometheus
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin
      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
    ports:
      - "127.0.0.1:3001:3000"

volumes:
  prometheus_data:
  grafana_data:
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To start the stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/monitoring/llm-grafana
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To verify it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose ps
curl http://127.0.0.1:9108/metrics | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 20
curl http://127.0.0.1:9090/-/ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grafana is available at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:3001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the first login, you need to configure a user account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful Grafana Queries
&lt;/h2&gt;

&lt;p&gt;In the Grafana panels, I used &lt;code&gt;Code&lt;/code&gt; mode instead of the visual query builder.&lt;/p&gt;

&lt;p&gt;Recorded responses:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Total tokens:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Latest response time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aider_ollama_last_wall_time_seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tokens per second for the latest response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aider_ollama_last_generation_tokens_per_second
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For KPIs, I used &lt;code&gt;Stat&lt;/code&gt; panels, while for trends I used &lt;code&gt;Time series&lt;/code&gt; panels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Daily Workflow
&lt;/h2&gt;

&lt;p&gt;Terminal 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~
python3 ~/ollama_usage_proxy.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terminal 2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/repos/my-project
aider14stats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terminal 3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/monitoring/llm-grafana
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terminal 4:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.venvs/aider-dashboard/bin/activate
streamlit run ~/aider_usage_dashboard.py &lt;span class="nt"&gt;--server&lt;/span&gt;.address 127.0.0.1 &lt;span class="nt"&gt;--server&lt;/span&gt;.port 8501
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup, I ended up with two complementary views:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Streamlit → operational debugging
Grafana   → stable monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is still an experimental setup. For a more mature environment, I would probably consolidate everything into a single tool.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
    </item>
    <item>
      <title>Setting Up a Local AI Coding Agent with Ollama and Aider (part 2)</title>
      <dc:creator>eleonorarocchi</dc:creator>
      <pubDate>Tue, 14 Jul 2026 20:48:00 +0000</pubDate>
      <link>https://dev.to/eleonorarocchi/setting-up-a-local-ai-coding-agent-with-ollama-and-aider-part-2-1ngk</link>
      <guid>https://dev.to/eleonorarocchi/setting-up-a-local-ai-coding-agent-with-ollama-and-aider-part-2-1ngk</guid>
      <description>&lt;h2&gt;
  
  
  Metrics
&lt;/h2&gt;

&lt;p&gt;Once I had Aider working with a local Ollama instance, there was still one thing missing: understanding what was actually happening under the hood.&lt;/p&gt;

&lt;p&gt;I wanted to see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how many tokens were being processed&lt;/li&gt;
&lt;li&gt;how long each response took&lt;/li&gt;
&lt;li&gt;how fast text generation was&lt;/li&gt;
&lt;li&gt;whether one model was slower than another&lt;/li&gt;
&lt;li&gt;whether the context window was growing too large&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aider alone didn't provide enough visibility. However, Ollama exposes several useful metrics in the final response returned by its API.&lt;/p&gt;

&lt;p&gt;So I decided to add a small local proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The request flow became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Aider
  ↓
Local proxy on port 11435
  ↓
Ollama on Windows (11434)
  ↓
qwen2.5-coder:14b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proxy performs two simple tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;forwards every request to Ollama&lt;/li&gt;
&lt;li&gt;stores the execution statistics in a JSONL file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The resulting log file is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/ollama_usage.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the Proxy
&lt;/h2&gt;

&lt;p&gt;Inside WSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/ollama_usage_proxy.py &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
#!/usr/bin/env python3
import json
import time
import urllib.request
from datetime import datetime, timezone
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler

UPSTREAM = "http://127.0.0.1:11434"
LOG_FILE = "/home/xxxxx/ollama_usage.jsonl"

def ns_to_s(value):
    if isinstance(value, (int, float)):
        return value / 1_000_000_000
    return None

def safe_div(a, b):
    if not a or not b:
        return None
    return a / b

class OllamaProxy(BaseHTTPRequestHandler):
    protocol_version = "HTTP/1.0"

    def log_message(self, format, *args):
        return

    def do_GET(self):
        self.forward()

    def do_POST(self):
        self.forward()

    def forward(self):
        length = int(self.headers.get("Content-Length", "0") or "0")
        body = self.rfile.read(length) if length else None

        url = UPSTREAM + self.path
        headers = {
            k: v for k, v in self.headers.items()
            if k.lower() not in {
                "host",
                "content-length",
                "connection",
                "accept-encoding",
            }
        }

        request = urllib.request.Request(
            url,
            data=body,
            headers=headers,
            method=self.command,
        )

        started = time.perf_counter()
        final_obj = None

        try:
            with urllib.request.urlopen(request, timeout=None) as response:
                self.send_response(response.status)
                self.send_header(
                    "Content-Type",
                    response.headers.get("Content-Type", "application/json"),
                )
                self.end_headers()

                for line in response:
                    self.wfile.write(line)
                    self.wfile.flush()

                    try:
                        obj = json.loads(line.decode("utf-8"))
                        if obj.get("done") is True:
                            final_obj = obj
                    except Exception:
                        pass

        except Exception as exc:
            self.send_error(502, f"Proxy error: {exc}")
            return

        wall_time_s = time.perf_counter() - started

        if not final_obj:
            return

        prompt_tokens = final_obj.get("prompt_eval_count")
        response_tokens = final_obj.get("eval_count")

        prompt_eval_s = ns_to_s(final_obj.get("prompt_eval_duration"))
        eval_s = ns_to_s(final_obj.get("eval_duration"))
        total_s = ns_to_s(final_obj.get("total_duration"))
        load_s = ns_to_s(final_obj.get("load_duration"))

        row = {
            "ts": datetime.now(timezone.utc).isoformat(),
            "endpoint": self.path,
            "method": self.command,
            "model": final_obj.get("model"),
            "done_reason": final_obj.get("done_reason"),
            "prompt_tokens": prompt_tokens,
            "response_tokens": response_tokens,
            "total_tokens": (prompt_tokens or 0) + (response_tokens or 0),
            "wall_time_s": round(wall_time_s, 3),
            "ollama_total_s": round(total_s, 3) if total_s is not None else None,
            "load_s": round(load_s, 3) if load_s is not None else None,
            "prompt_eval_s": round(prompt_eval_s, 3) if prompt_eval_s is not None else None,
            "generation_s": round(eval_s, 3) if eval_s is not None else None,
            "prompt_tokens_per_s": round(safe_div(prompt_tokens, prompt_eval_s), 2)
            if safe_div(prompt_tokens, prompt_eval_s) else None,
            "generation_tokens_per_s": round(safe_div(response_tokens, eval_s), 2)
            if safe_div(response_tokens, eval_s) else None,
        }

        with open(LOG_FILE, "a", encoding="utf-8") as f:
            f.write(json.dumps(row, ensure_ascii=False) + "&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;")

if __name__ == "__main__":
    server = ThreadingHTTPServer(("127.0.0.1", 11435), OllamaProxy)
    print("Ollama usage proxy listening on http://127.0.0.1:11435")
    print(f"Forwarding requests to {UPSTREAM}")
    print(f"Writing statistics to {LOG_FILE}")
    server.serve_forever()
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then make it executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/ollama_usage_proxy.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Starting the Proxy
&lt;/h2&gt;

&lt;p&gt;In a WSL terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~
python3 ~/ollama_usage_proxy.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leave this terminal running while using Aider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Aider Through the Proxy
&lt;/h2&gt;

&lt;p&gt;In a second WSL terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/repos/my-project
&lt;span class="nv"&gt;OLLAMA_API_BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://127.0.0.1:11435 aider &lt;span class="nt"&gt;--model&lt;/span&gt; ollama_chat/qwen2.5-coder:14b &lt;span class="nt"&gt;--no-show-model-warnings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The distinction is important:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;11434 = Direct connection to Ollama (no metrics)
11435 = Proxy enabled (metrics collected)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Inspecting the JSONL Log
&lt;/h2&gt;

&lt;p&gt;After Aider generates a response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 5 ~/ollama_usage.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make the output easier to read, I installed &lt;code&gt;jq&lt;/code&gt;, a powerful open-source command-line tool for parsing, filtering, formatting, and manipulating JSON data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 5 ~/ollama_usage.jsonl | jq &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A typical log entry looks like this:&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;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"qwen2.5-coder:14b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;310&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1590&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"wall_time_s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;42.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"generation_s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;37.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"generation_tokens_per_s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;8.2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating an Alias
&lt;/h2&gt;

&lt;p&gt;To avoid accidentally connecting Aider to the wrong port, I created a shell alias:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'alias aider14stats="OLLAMA_API_BASE=http://127.0.0.1:11435 aider --model ollama_chat/qwen2.5-coder:14b --no-show-model-warnings"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From that point on, starting Aider was as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/repos/my-project
aider14stats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Logging should be part of the design from the very beginning.&lt;/p&gt;

&lt;p&gt;Without metrics, a local model may simply &lt;em&gt;feel&lt;/em&gt; slow. With proper measurements, I can understand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;how much input I'm sending
how much output I'm receiving
how long each request takes
how many tokens per second are being generated
whether the model was already loaded into memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Having access to this information completely changes the way I can evaluate and compare locally hosted LLMs.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>Setting Up a Local AI Coding Agent with Ollama and Aider</title>
      <dc:creator>eleonorarocchi</dc:creator>
      <pubDate>Sat, 11 Jul 2026 13:41:12 +0000</pubDate>
      <link>https://dev.to/eleonorarocchi/setting-up-a-local-ai-coding-agent-with-ollama-and-aider-1jdi</link>
      <guid>https://dev.to/eleonorarocchi/setting-up-a-local-ai-coding-agent-with-ollama-and-aider-1jdi</guid>
      <description>&lt;p&gt;Over the past few months, I've experimented with several workflows for using LLMs in software development. In this article, I describe the local setup I've validated on my own PC: Ollama running on Windows, Aider inside Ubuntu on WSL2, and a code-focused model running entirely on the local machine.&lt;/p&gt;

&lt;p&gt;The machine I used for my first experiment runs Windows 11 with an Intel Core i7-1355U CPU, 48 GiB of RAM, and no dedicated NVIDIA GPU. It's generally a capable machine, but not particularly well suited for AI inference. Because it lacks a dedicated GPU, I had to run the model in CPU-only mode.&lt;/p&gt;

&lt;p&gt;The goal of this setup was to create an environment where both the source code and the data remain entirely on the local machine—far from the cloud-based frontier model approach.&lt;/p&gt;

&lt;p&gt;From an architectural standpoint, I decided to keep Ollama on Windows because I also use the installed models with other Windows applications, and I wanted to avoid maintaining duplicate model installations. Aider, on the other hand, runs inside WSL, where installation and configuration are simpler and the Linux command-line environment is more convenient.&lt;/p&gt;

&lt;p&gt;Ollama remains exposed on the local port 11434. Aider, running inside Ubuntu WSL2, connects to Ollama through the &lt;code&gt;OLLAMA_API_BASE&lt;/code&gt; environment variable.&lt;/p&gt;

&lt;p&gt;This approach avoids duplicating models inside WSL while keeping development repositories in the Linux filesystem under &lt;code&gt;~/repos&lt;/code&gt;, where Git and other command-line tools work much more naturally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local Setup
&lt;/h2&gt;

&lt;p&gt;Since this machine is not an AI workstation, the solution had to be realistic. That meant avoiding massive GPU-hosted models, cloud APIs, and overly complex automation.&lt;/p&gt;

&lt;p&gt;The final architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Windows
 ├─ Ollama
 │   └─ qwen2.5-coder:14b
 │
 └─ WSL Ubuntu
     ├─ Aider
     ├─ Git
     └─ Development repositories
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keeping &lt;strong&gt;Ollama on Windows&lt;/strong&gt; while running &lt;strong&gt;Aider inside WSL&lt;/strong&gt; was a natural choice. I already use Ollama with several Windows applications, so duplicating the models inside WSL made little sense. At the same time, I much prefer working with the Linux command line for software development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Ollama on Windows
&lt;/h2&gt;

&lt;p&gt;I installed Ollama on Windows and downloaded the primary coding model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pull&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;qwen2.5-coder:14b&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also installed the smaller model for quicker testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pull&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;qwen2.5-coder:7b&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To verify the installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The WSL-to-Windows Connectivity Issue
&lt;/h2&gt;

&lt;p&gt;Initially, Aider running inside WSL couldn't communicate with Ollama running on Windows.&lt;/p&gt;

&lt;p&gt;To make it work, I had to configure WSL to use &lt;strong&gt;mirrored networking&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;From PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;notepad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="nx"&gt;\.wslconfig&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I added the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[wsl2]&lt;/span&gt;
&lt;span class="py"&gt;networkingMode&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;mirrored&lt;/span&gt;
&lt;span class="py"&gt;dnsTunneling&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;firewall&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;autoProxy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, I restarted WSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;wsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--shutdown&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After reopening Ubuntu, I tested the connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://127.0.0.1:11434/api/tags
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response confirmed that WSL could successfully reach the Ollama server running on Windows at &lt;code&gt;127.0.0.1:11434&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Aider in WSL
&lt;/h2&gt;

&lt;p&gt;Inside Ubuntu/WSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; git python3 python3-pip python3-venv pipx curl
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pipx ensurepath
&lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nv"&gt;$SHELL&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
pipx &lt;span class="nb"&gt;install &lt;/span&gt;aider-chat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To verify the installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aider &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also configured Git so I could version-control my experiments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; user.name &lt;span class="s2"&gt;"xxxx"&lt;/span&gt;
git config &lt;span class="nt"&gt;--global&lt;/span&gt; user.email &lt;span class="s2"&gt;"xxxxxxx@xxxxx.xx"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the First Repository
&lt;/h2&gt;

&lt;p&gt;I created a simple test project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/repos
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/repos
&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-project
&lt;span class="nb"&gt;cd &lt;/span&gt;my-project
git init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I launched Aider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/repos/my-project
&lt;span class="nv"&gt;OLLAMA_API_BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://127.0.0.1:11434 aider &lt;span class="nt"&gt;--model&lt;/span&gt; ollama_chat/qwen2.5-coder:14b &lt;span class="nt"&gt;--no-show-model-warnings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Aider displayed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Aider v0.86.2
Model: ollama_chat/qwen2.5-coder:14b
Git repo: .git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I finally knew the basic setup was working correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Opening the Repository from Windows Explorer
&lt;/h2&gt;

&lt;p&gt;For convenience, I also wanted to access the repository directly from Windows Explorer.&lt;/p&gt;

&lt;p&gt;From inside WSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;explorer.exe &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command opens the current WSL directory directly in Windows File Explorer.&lt;/p&gt;

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

&lt;p&gt;Once I had Aider working with a local Ollama instance, there was still one thing I wanted to understand better: what was happening under the hood. How many tokens were being processed? How long did inference take? What kind of throughput could I expect?&lt;/p&gt;

&lt;p&gt;We'll cover all of that in the next installment. 😉&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>If AI writes code, what is our job now?</title>
      <dc:creator>eleonorarocchi</dc:creator>
      <pubDate>Wed, 01 Jul 2026 10:38:16 +0000</pubDate>
      <link>https://dev.to/eleonorarocchi/if-ai-writes-code-what-is-our-job-now-106a</link>
      <guid>https://dev.to/eleonorarocchi/if-ai-writes-code-what-is-our-job-now-106a</guid>
      <description>&lt;p&gt;Anthropic published a very interesting article on &lt;strong&gt;recursive self-improvement&lt;/strong&gt;: the possibility that AI systems will increasingly contribute to building better future versions of themselves.&lt;/p&gt;

&lt;p&gt;Article: &lt;a href="https://www.anthropic.com/institute/recursive-self-improvement" rel="noopener noreferrer"&gt;https://www.anthropic.com/institute/recursive-self-improvement&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is a huge topic, almost science fiction: AI developing AI, increasingly fast improvement cycles, systems capable of automating growing parts of research and development.&lt;/p&gt;

&lt;p&gt;But reading the article as a developer, I think the most interesting question is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Will AI replace us?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The more useful question today is another one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If AI can write, test, refactor, and review code better and better, what is the real role of humans in software development?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For years, we have measured a developer's work partly by their ability to produce code: building features, fixing bugs, optimizing parts of a system, handling refactoring, and doing reviews.&lt;/p&gt;

&lt;p&gt;Today, however, an increasingly large part of these activities can be accelerated by AI tools.&lt;/p&gt;

&lt;p&gt;This means that the value of the developer is shifting from "doing" to "deciding well what should be done."&lt;/p&gt;

&lt;p&gt;One of the most important points in Anthropic's article is that AI becomes very strong when the goal is clear, especially on well-defined tasks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But are we, as developers, still truly understanding what is being produced?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because if AI generates code that works, but we can no longer explain it, maintain it, or assess its risks, then we have not really increased our productivity. We have only pushed technical debt a little further down the road.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottleneck
&lt;/h2&gt;

&lt;p&gt;In many teams, the historical problem was producing enough code. But with today's AI tools, the bottleneck shifts: it becomes the human ability to review, validate, and understand.&lt;/p&gt;

&lt;p&gt;If a tool can generate in a few minutes what used to take hours, the critical point becomes how quickly we can understand, verify, and maintain it.&lt;/p&gt;

&lt;p&gt;This is where the developer of the near future is defined: someone who owns judgment skills that cannot be delegated to AI.&lt;/p&gt;

&lt;p&gt;And so my provocation is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if part of the low productivity of those teams does not depend on the tools, but on the fact that they keep working as if we were still in 2015?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Today we have tools that can accelerate coding, debugging, testing, review, documentation, and learning. But if we use them with old mindsets, rigid processes, and poorly collaborative habits, the gain remains limited.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>developer</category>
      <category>llm</category>
    </item>
    <item>
      <title>Generator-Evaluator Loops for AI Agents</title>
      <dc:creator>eleonorarocchi</dc:creator>
      <pubDate>Fri, 22 May 2026 05:26:00 +0000</pubDate>
      <link>https://dev.to/eleonorarocchi/generator-evaluator-loops-for-ai-agents-4kd2</link>
      <guid>https://dev.to/eleonorarocchi/generator-evaluator-loops-for-ai-agents-4kd2</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Separating the generator from the evaluator improves quality and reduces premature self-validation.&lt;/li&gt;
&lt;li&gt;The loop works best when feedback is explicit and based on clear rubrics, especially for subjective or complex tasks.&lt;/li&gt;
&lt;li&gt;It is useful when the task has high value; for simple or easily testable tasks, it can become overengineering.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How to Separate Production and Evaluation in Tasks Without Ground Truth
&lt;/h2&gt;

&lt;p&gt;If self-evaluation is a recurring failure mode in AI agents, the most natural solution is to separate the role of producing from the role of evaluating.&lt;/p&gt;

&lt;p&gt;This is the principle behind generator-evaluator loops: one agent produces an output, another evaluates it according to explicit criteria, feedback is sent back to the generator, and the system iterates until the result reaches an acceptable threshold.&lt;/p&gt;

&lt;p&gt;The pattern is simple to describe, but powerful, especially in domains where stable ground truth does not exist: design, writing, UX, naming, strategy, documentation, software architecture, complex refactoring.&lt;/p&gt;

&lt;p&gt;In these cases, however, it is not enough to ask the model to "do better." You need to build an environment where improvement is driven by separate, structured, and repeatable critique.&lt;/p&gt;

&lt;h2&gt;
  
  
  From a Single Agent to an Agentic System
&lt;/h2&gt;

&lt;p&gt;A single agent tends to merge three different roles: it plans the work, produces the output, and finally evaluates the result.&lt;/p&gt;

&lt;p&gt;This fusion is convenient but fragile. While it may work for short tasks, in longer ones it often leads to premature convergence, because the same agent chooses a direction, develops it, then positively self-evaluates and closes the task.&lt;/p&gt;

&lt;p&gt;To create a more robust system, these roles should be separated into at least three distinct agents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the planner, which breaks the request into manageable parts and defines priorities, constraints, and work sequence;&lt;/li&gt;
&lt;li&gt;the generator, which produces the artifact (e.g. code, interface, document, proposal, strategy, ...);&lt;/li&gt;
&lt;li&gt;the evaluator, which judges the output without having participated in its generation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation almost resembles a miniature organizational structure, and in fact, similarly, it helps reduce coupling between decision-making, production, and approval. As in a company, each role has a different objective, and this difference introduces useful friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Analogy with GANs
&lt;/h2&gt;

&lt;p&gt;The most immediate parallel is with Generative Adversarial Networks (GANs).&lt;/p&gt;

&lt;p&gt;In a classic GAN, there are two neural networks: a generator and a discriminator. The generator produces synthetic data, for example images, while the discriminator receives both real and generated data and attempts to distinguish between them. In this way, the generator improves by trying to produce outputs plausible enough to fool the discriminator, while the discriminator improves by becoming better at detecting artificial outputs.&lt;/p&gt;

&lt;p&gt;This idea has been applied across many domains: image generation, face synthesis, super-resolution, computer vision, synthetic data generation, image-to-image translation, music, and video. Examples include models such as StyleGAN, CycleGAN, TextGAN, and MuseGAN, which show how the generative-adversarial principle can be adapted to different forms of content.&lt;/p&gt;

&lt;p&gt;In our case-AI agents-the analogy is useful because it captures an architectural intuition: a generative system improves when exposed to separate judgment.&lt;/p&gt;

&lt;p&gt;There are, however, important aspects that should not be confused with the comparison, because a generator-evaluator loop is not a GAN in the technical sense.&lt;/p&gt;

&lt;p&gt;In real GANs, there is a mathematical loss function: the discriminator is trained on real data, while the generator receives an optimization signal through gradients.&lt;/p&gt;

&lt;p&gt;In AI agents, by contrast, feedback is linguistic and heuristic: the evaluator does not directly update the generator's weights, nor does it necessarily have access to a dataset of real examples.&lt;/p&gt;

&lt;p&gt;When an evaluator judges a landing page, a piece of writing, or a strategy, it is not distinguishing "true" from "false" in the GAN sense. It is estimating how closely the output aligns with a set of preferences, criteria, and conventions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Subjectivity Evaluatable
&lt;/h2&gt;

&lt;p&gt;The core challenge is turning vague judgments into observable criteria.&lt;/p&gt;

&lt;p&gt;Saying "this UI is ugly" does not help an agent improve-just as saying the same thing to a human designer would not.&lt;/p&gt;

&lt;p&gt;Saying "the visual hierarchy does not sufficiently distinguish between the primary action and secondary content" is much more useful.&lt;/p&gt;

&lt;p&gt;An effective evaluator needs what are commonly called evaluation rubrics.&lt;/p&gt;

&lt;p&gt;In frontend design, for example, a rubric might include four dimensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Design quality&lt;/strong&gt;, which measures whether the result feels like a coherent system or merely an assembly of components. It evaluates visual identity, creative direction, color usage, typography, rhythm, and layout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Originality&lt;/strong&gt;, which measures whether intentional choices are present or whether the output looks derived from templates, library defaults, and generic patterns (this includes recognizing AI slop: predictable gradients, white cards, interchangeable hero sections, stock icons, personality-less compositions).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Craft&lt;/strong&gt;, which measures execution: spacing, contrast, alignment, typographic hierarchy, color consistency, and attention to detail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functionality&lt;/strong&gt;, which measures usability: whether users understand what to do, find the primary actions, navigate without ambiguity, and complete intended tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These criteria do not all need to carry the same weight. In many cases, models are already reasonably strong in craft and functionality; they produce orderly layouts, readable text, and understandable structures. The recurring issue is often the lack of originality and direction.&lt;/p&gt;

&lt;p&gt;For this reason, an evaluator focused on real quality should penalize not only errors, but also blandness.&lt;/p&gt;

&lt;p&gt;The same principle can be applied to other domains.&lt;/p&gt;

&lt;p&gt;For writing, a rubric might evaluate thesis strength, argumentative structure, informational density, rhythm, specificity, voice, redundancy, and the ability to anticipate objections.&lt;/p&gt;

&lt;p&gt;An AI-generated text may be correct, fluent, and well formatted, yet still fail to say anything memorable. In that case, a useful evaluator should be able to distinguish between superficial clarity and real argumentative value.&lt;/p&gt;

&lt;p&gt;For strategy, a rubric might evaluate diagnostic quality, explicit assumptions, trade-offs, feasibility, prioritization, risks, dependencies, metrics, and contextual alignment.&lt;/p&gt;

&lt;p&gt;And what about code? Beyond tests, a rubric should assess simplicity, maintainability, consistency with the codebase, error handling, extensibility, technical debt introduced, readability, and impact on existing abstractions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Operational Loop
&lt;/h2&gt;

&lt;p&gt;A typical generator-evaluator loop follows a simple sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the system receives a specification;&lt;/li&gt;
&lt;li&gt;the planner breaks it into subtasks and defines success criteria;&lt;/li&gt;
&lt;li&gt;the generator produces a first version of the output;&lt;/li&gt;
&lt;li&gt;the evaluator inspects the artifact using explicit criteria and, when possible, real tools such as browsers, test runners, screenshots, parsers, linters, metrics, documentation, or access to the codebase;&lt;/li&gt;
&lt;li&gt;the evaluator assigns scores, identifies issues, and produces actionable feedback;&lt;/li&gt;
&lt;li&gt;the generator receives the feedback and iterates.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The cycle continues until the output exceeds a threshold, the budget is exhausted, or the system determines that human intervention is required.&lt;/p&gt;

&lt;p&gt;The core of the loop is actionable feedback, because the evaluator must not only detect failure, but also make it correctable.&lt;/p&gt;

&lt;p&gt;A generator-evaluator loop is fundamentally a matter of harness engineering, because the evaluator must have access to the right tools.&lt;/p&gt;

&lt;p&gt;That said, an evaluator does not automatically improve simply because it is separated from the generator. For it to work, it must be calibrated using explicit criteria, scoring scales, potentially few-shot examples, thresholds, and similar mechanisms.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use This Pattern
&lt;/h2&gt;

&lt;p&gt;A generator-evaluator loop has a cost: more model calls, more tokens, more latency, more orchestration, and more complexity. As a result, it is impossible to apply it everywhere.&lt;/p&gt;

&lt;p&gt;As a rule of thumb, if a reliable automated test exists, it is usually better to use it.&lt;/p&gt;

&lt;p&gt;And if the task is simple or low-value, a complex loop may genuinely become overengineering.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
    </item>
    <item>
      <title>Why AI Agents can’t judge themselves</title>
      <dc:creator>eleonorarocchi</dc:creator>
      <pubDate>Fri, 15 May 2026 05:19:00 +0000</pubDate>
      <link>https://dev.to/eleonorarocchi/why-ai-agents-cant-judge-themselves-24fc</link>
      <guid>https://dev.to/eleonorarocchi/why-ai-agents-cant-judge-themselves-24fc</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AI agents tend to overestimate the quality of their own outputs when there is no external verification criterion. In subjective tasks (design, writing, UX, naming, strategy), simply asking the model to "reflect" is not enough: it often remains trapped in the same trajectory that produced the first plausible solution, leading to weak critiques and superficial improvements.&lt;/li&gt;
&lt;li&gt;Achieving real quality requires designing the runtime around the model: tests, rubrics, separate evaluators, external tools, and generator-evaluator loops that introduce critical distance between the system that produces the output and the one that approves it.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Internal Feedback Is Not Enough in Subjective Tasks
&lt;/h2&gt;

&lt;p&gt;Sometimes, when you ask a model to evaluate a response it previously generated, it will rate it as good even when it clearly is not. Buggy code gets labeled "production-ready"; a generic layout is described as "modern and coherent"; a technically correct but flat piece of writing is called "clear, incisive, and well-structured."&lt;/p&gt;

&lt;p&gt;This behavior becomes especially evident when the task lacks binary verification.&lt;/p&gt;

&lt;p&gt;If the agent has to write a function and there is a reliable test suite available, the system has access to an external oracle: the test either passes or fails.&lt;/p&gt;

&lt;p&gt;But as soon as the task moves into domains such as design, writing, naming, UX, strategy, or product architecture, quality can no longer be reduced to an &lt;code&gt;assert&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is where the self-evaluation problem in AI agents emerges: the same system that produces the output struggles to judge it with enough critical distance. And, if we think about it, humans often behave the same way.&lt;/p&gt;

&lt;p&gt;The point, however, is not that LLMs have ego, self-esteem, or a desire to appear competent. Saying that agents "self-promote" is a useful shortcut, but technically inaccurate. A model is not trying to convince us that its output is good. More often, it simply remains inside the same probabilistic trajectory that generated the artifact in the first place.&lt;/p&gt;

&lt;p&gt;If we ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Generate a landing page for a SaaS product.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And immediately afterward:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Evaluate the quality of the landing page.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;we have not really created two distinct processes. We have asked the same model to continue reasoning within the same semantic space, with the same context, assumptions, and implicit orientation toward completing the task.&lt;/p&gt;

&lt;p&gt;The result is often an evaluation that is overly generous, poorly discriminative, and not very useful for driving meaningful improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tasks With an Oracle and Tasks Without One
&lt;/h2&gt;

&lt;p&gt;We can distinguish between two classes of tasks.&lt;/p&gt;

&lt;p&gt;The first includes tasks with an external oracle. These are cases where quality can be verified relatively objectively: an automated test, a query returning an expected result, a formal constraint, a compiler, syntactic validation, or a numerical measurement.&lt;/p&gt;

&lt;p&gt;In software engineering, many tasks fall at least partially into this category. Code can be evaluated through unit tests, integration tests, type checkers, linters, benchmarks, and static analysis. These tools do not fully capture software quality, but they provide strong signals. If an agent produces code that does not compile or breaks a test suite, the system does not need to "guess" that something is wrong: it knows.&lt;/p&gt;

&lt;p&gt;The second class includes tasks without a clear oracle. Here, quality is subjective, multidimensional, or context-dependent. A UI can be technically correct but visually uninspired. A text can be grammatically flawless but lack a real thesis. A strategy can be well formatted yet impossible to execute. A naming proposal can be understandable but forgettable.&lt;/p&gt;

&lt;p&gt;In these cases, the problem is not just verifying whether the output is correct, but determining whether it is actually good.&lt;/p&gt;

&lt;p&gt;And unfortunately, "good" does not mean one single, clearly identifiable thing.&lt;/p&gt;

&lt;p&gt;In design, it may mean visual coherence, originality, hierarchy, usability, or identity; in writing, clarity, density, rhythm, argumentative strength, or voice; in strategy, accurate diagnosis, explicit trade-offs, feasibility, specificity, and contextual alignment.&lt;/p&gt;

&lt;p&gt;When an external oracle is missing, the agent tends to rely on its own linguistic evaluation. And that is exactly where the system becomes fragile.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Failure Mode: Premature Convergence
&lt;/h2&gt;

&lt;p&gt;The most common failure mode is not catastrophic error, but premature convergence.&lt;/p&gt;

&lt;p&gt;The agent produces a plausible solution, refines it superficially, and declares it sufficient. The result is not necessarily wrong. Often, it is worse: mediocre but defensible.&lt;/p&gt;

&lt;p&gt;This "plausible mediocrity" is difficult to detect because it contains many superficial signals of quality.&lt;/p&gt;

&lt;p&gt;An AI-generated landing page will probably include a hero section, a CTA, a feature grid, a few cards, a responsive layout, a pleasant color palette, and tidy copy. A strategy document will include neatly titled sections, bullet points, frameworks, and recommendations. A refactoring will contain cleaner names and a few extra abstractions.&lt;/p&gt;

&lt;p&gt;But all of this can still remain generic.&lt;/p&gt;

&lt;p&gt;The agent tends to improve what it has already produced instead of questioning whether the direction itself is correct. It polishes the first solution instead of challenging it. It adds local coherence, not global quality.&lt;/p&gt;

&lt;p&gt;This is where self-evaluation fails: not because the model cannot recognize any errors, but because it often does not apply criticism strong enough to break away from the first acceptable solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Limits of Reflective Prompting
&lt;/h2&gt;

&lt;p&gt;One of the earliest responses to this problem was reflective prompting: asking the model to critique its own output, identify issues, propose improvements, and iterate.&lt;/p&gt;

&lt;p&gt;This approach works to some extent because it can eliminate obvious errors, improve clarity, fix inconsistencies, and add missing details. However, its main limitation is that the critique remains inside the same process that generated the output.&lt;/p&gt;

&lt;p&gt;Prompts such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reflect on your work and improve it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Identify any problems in the previous response.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;often produce generic feedback:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"It could be more specific";&lt;/li&gt;
&lt;li&gt;"Clarity could be improved";&lt;/li&gt;
&lt;li&gt;"I would add more concrete examples";&lt;/li&gt;
&lt;li&gt;"The structure is already solid, but it could be refined."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These observations are true, but weak, and they rarely lead to a substantial change in direction.&lt;/p&gt;

&lt;p&gt;For simple tasks this may be enough, but for high-value tasks it often is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Runtime Matters
&lt;/h2&gt;

&lt;p&gt;This problem contributed to the rise of harness engineering: designing the runtime around the model.&lt;/p&gt;

&lt;p&gt;As I described in previous articles about &lt;a href="https://dev.to/eleonorarocchi/harness-engineering-la-parte-piu-importante-degli-agenti-ai-4jnd"&gt;harnesses&lt;/a&gt;, the core idea is that the performance of an agentic system depends not only on the model itself, but also on the operational environment in which the model works. What matters is how the prompt is constructed, which tools are available, how context is managed, how intermediate states are stored, how tests are executed, how feedback is orchestrated, when the system decides to iterate, and when it decides to stop.&lt;/p&gt;

&lt;p&gt;In modern agentic systems, the model is just one component. Final behavior emerges from the interaction between the model, tools, memory, context, schedulers, evaluators, acceptance criteria, and retry mechanisms.&lt;/p&gt;

&lt;p&gt;This shift in perspective is fundamental. If the model struggles to evaluate itself, the solution is not necessarily to wait for a better model. It is to design a runtime that makes the evaluation process less fragile.&lt;/p&gt;

&lt;p&gt;In coding, this may mean running tests, reading errors, applying patches, and retrying. In design, it may mean generating screenshots, navigating the interface, and verifying interactive states. In writing, it may mean using editorial rubrics, comparing versions, and evaluating density and redundancy. In strategy, it may mean making assumptions explicit and testing alternative scenarios.&lt;/p&gt;

&lt;p&gt;The runtime introduces signals that the model alone does not reliably produce.&lt;/p&gt;

&lt;h2&gt;
  
  
  Critical Distance as an Architectural Requirement
&lt;/h2&gt;

&lt;p&gt;The self-evaluation problem can be summarized like this: generation and evaluation are too close to each other.&lt;/p&gt;

&lt;p&gt;Critical distance can be introduced in many ways. Sometimes changing the prompt, role, or critique format is enough; other times it requires a different model, a different temperature, a stricter rubric, few-shot examples, external tools, or a separate agent.&lt;/p&gt;

&lt;p&gt;The principle remains the same: the system must create a separation between the entity that produces and the entity that approves.&lt;/p&gt;

&lt;p&gt;This separation does not guarantee perfect evaluation, but it reduces the risk that the agent settles for the first plausible solution.&lt;/p&gt;

&lt;p&gt;This naturally leads to the generator-evaluator pattern: one agent produces, another evaluates, feedback returns to the first, and the cycle continues until the output surpasses a threshold.&lt;/p&gt;

&lt;p&gt;It is not always necessary: for simple tasks it can become overengineering.&lt;/p&gt;

&lt;p&gt;But for subjective, long, or high-value tasks, it becomes one of the most useful patterns in agent engineering.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
    </item>
    <item>
      <title>How Stripe, Shopify, and Airbnb Build AI Harnesses</title>
      <dc:creator>eleonorarocchi</dc:creator>
      <pubDate>Sat, 09 May 2026 07:04:00 +0000</pubDate>
      <link>https://dev.to/eleonorarocchi/how-stripe-shopify-and-airbnb-build-ai-harnesses-1i6l</link>
      <guid>https://dev.to/eleonorarocchi/how-stripe-shopify-and-airbnb-build-ai-harnesses-1i6l</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;There is no single model of harness engineering.&lt;/li&gt;
&lt;li&gt;OpenAI builds repository-centered harnesses, Anthropic focuses on agent cognitive continuity, while companies like Stripe, Shopify, and Airbnb develop vertical harnesses built around compliance, context, and action verification.&lt;/li&gt;
&lt;li&gt;Harness engineering is becoming a domain-specific discipline, shaped by the type of risk each company needs to control.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why There Is No Single Harness: Stripe, Shopify, Airbnb, and the Industrial Fragmentation of Agent Engineering
&lt;/h2&gt;

&lt;p&gt;After observing OpenAI's repository harness and Anthropic's runtime harness (if you haven't done so already, read my articles: &lt;a href="https://dev.to/eleonorarocchi/openai-and-the-new-cognitive-architecture-of-software-repositories-383m"&gt;OpenAI and the New Cognitive Architecture of Software Repositories&lt;/a&gt; e &lt;a href="https://dev.to/eleonorarocchi/anthropic-and-the-runtime-harness-for-persistent-agents-4mf2"&gt;Anthropic and the Runtime Harness for Persistent Agents&lt;/a&gt;), one might expect the industry to be converging toward a fairly clear formula: define memory, tools, feedback loops, constraints, and let the agents work.&lt;/p&gt;

&lt;p&gt;In reality, the opposite is happening: the more public big-tech case studies become, the more it becomes clear that the word &lt;em&gt;harness&lt;/em&gt; is starting to cover profoundly different architectures.&lt;/p&gt;

&lt;p&gt;I find this to be the most interesting signal of the sector's maturation, because it means we are no longer witnessing the birth of a standard, but rather the emergence of multiple implementation paradigms.&lt;/p&gt;

&lt;p&gt;The comparative analyses published about companies like Stripe, Shopify, and Airbnb demonstrate this very clearly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Point Is Not Model Capability. It's the Cost of Failure.
&lt;/h2&gt;

&lt;p&gt;As long as we talk about coding agents in the abstract, there is a tendency to imagine that the problem is singular: making the model more reliable.&lt;/p&gt;

&lt;p&gt;However, in industrial environments, reliability is not a neutral category; it depends on what the company considers tolerable or intolerable. That is where the divergence begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stripe: The Harness as a Compliance Boundary
&lt;/h2&gt;

&lt;p&gt;In the financial domain, the problem is not only producing a correct modification, but producing a modification that does not violate policies, introduce vulnerabilities, alter critical transactional flows, and remains fully auditable.&lt;/p&gt;

&lt;p&gt;In this context, the harness tends to become an approval gate, with automated validation, side-effect simulation, and above all, compliance controls.&lt;/p&gt;

&lt;p&gt;The agent does not operate in an open environment, but inside a risk-clearing chamber.&lt;/p&gt;

&lt;p&gt;The harness is primarily a containment boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shopify: Harnesses for Context Distribution
&lt;/h2&gt;

&lt;p&gt;Shopify's problem is almost the opposite: the commerce domain is hyper-fragmented, with different themes, plugins, merchant logics, and unpredictable customizations.&lt;/p&gt;

&lt;p&gt;The primary risk, beyond causing damage, is producing something generically correct but locally useless.&lt;/p&gt;

&lt;p&gt;For this reason, the harness must excel at contextual retrieval, access to internal documentation, merchant-state simulation, and precise distribution of relevant information.&lt;/p&gt;

&lt;p&gt;The model must not only be safe, but operate with an accurate understanding of the merchant's specific context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Airbnb: Harnesses as Perceptual Verifiability
&lt;/h2&gt;

&lt;p&gt;In customer-facing and UI-heavy workflows, the problem changes once again, because an agent can propose a technically reasonable modification while still breaking selectors, navigation, UX flows, or intermediate states.&lt;/p&gt;

&lt;p&gt;In cases like Airbnb, the harness emphasizes browser instrumentation, screenshot verification, replayability, and control over executed actions.&lt;/p&gt;

&lt;p&gt;The core question becomes: does the action actually produce the intended effect in the user environment?&lt;/p&gt;

&lt;p&gt;The harness therefore becomes a perceptual surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Best Practices to a Domain-Specific Discipline
&lt;/h2&gt;

&lt;p&gt;What these cases show is that a harness is not a universal checklist of components.&lt;/p&gt;

&lt;p&gt;A harness is a response to the failure modes that each organization considers economically most dangerous:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for OpenAI, the risk is codebase entropy;&lt;/li&gt;
&lt;li&gt;for Anthropic, it is cognitive drift;&lt;/li&gt;
&lt;li&gt;for Stripe, regulatory side effects;&lt;/li&gt;
&lt;li&gt;for Shopify, the loss of situational context;&lt;/li&gt;
&lt;li&gt;for Airbnb, the non-verifiability of actions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same word, completely different problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Maturity of the Industry Is This Fragmentation
&lt;/h2&gt;

&lt;p&gt;We often interpret fragmentation as a lack of standards. But I would argue the opposite can also be true: when a discipline is young, everyone uses the same generic formulas; as it matures, specialized architectures begin to emerge.&lt;/p&gt;

&lt;p&gt;That is perhaps exactly what is happening with harness engineering.&lt;/p&gt;

&lt;p&gt;Just like choosing the best TypeScript framework, we are now entering the phase where the real question becomes: which harness architecture is most coherent with the type of risk my agent cannot afford to tolerate?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
    </item>
    <item>
      <title>Anthropic and the Runtime Harness for Persistent Agents</title>
      <dc:creator>eleonorarocchi</dc:creator>
      <pubDate>Fri, 01 May 2026 05:13:00 +0000</pubDate>
      <link>https://dev.to/eleonorarocchi/anthropic-and-the-runtime-harness-for-persistent-agents-4mf2</link>
      <guid>https://dev.to/eleonorarocchi/anthropic-and-the-runtime-harness-for-persistent-agents-4mf2</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Anthropic shows that the real challenge for AI agents is not starting a task, but staying coherent throughout long executions.&lt;/li&gt;
&lt;li&gt;Avoiding cognitive drift requires a runtime harness built on external memory, checkpoints, and continuous re-anchoring.&lt;/li&gt;
&lt;li&gt;The next frontier is not autonomy alone: it is cognitive continuity.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Anthropic and the Runtime Harness: the Real Problem with Agents Is Not Acting, but Not Getting Lost While They Act
&lt;/h2&gt;

&lt;p&gt;If &lt;a href="https://dev.to/eleonorarocchi/openai-and-the-new-cognitive-architecture-of-software-repositories-383m"&gt;the OpenAI case&lt;/a&gt; showed how a repository can be rethought to become readable for agents, the contribution published by Anthropic in &lt;a href="https://www.anthropic.com/engineering/harness-design-long-running-apps" rel="noopener noreferrer"&gt;&lt;em&gt;Harness design for long-running application development&lt;/em&gt;&lt;/a&gt; opens an even more delicate question: what happens when the challenge is no longer how to start a task well, but how to keep it alive for hours?&lt;/p&gt;

&lt;p&gt;Because this is where many agentic systems truly begin to break.&lt;/p&gt;

&lt;p&gt;Not at the first tool call, nor at the first planning step, but perhaps at the twentieth minute-when context starts to thin out, micro-errors begin to accumulate, and the agent keeps acting while preserving only the illusion of coherence.&lt;/p&gt;

&lt;p&gt;In its article &lt;a href="https://www.anthropic.com/engineering/harness-design-long-running-apps" rel="noopener noreferrer"&gt;&lt;em&gt;Harness design for long-running application development&lt;/em&gt;&lt;/a&gt;, Anthropic puts its finger exactly on this point: the frontier of agent engineering is not simply autonomy, but the persistence of autonomy over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Most Underestimated Failure Mode: Cognitive Drift
&lt;/h2&gt;

&lt;p&gt;Many agents appear to work well as long as we observe them on short tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generating a component;&lt;/li&gt;
&lt;li&gt;fixing a function;&lt;/li&gt;
&lt;li&gt;calling two or three tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But when the task stretches across dozens of files, multiple review phases, intermediate validations, and distributed dependencies, a phenomenon begins that is very familiar to those who use them in real settings: the agent continues to produce output, but progressively loses the center of its own intention.&lt;/p&gt;

&lt;p&gt;Anthropic treats this as a structural problem, not as a simple "model limitation": and this is precisely where the runtime harness emerges.&lt;/p&gt;

&lt;h2&gt;
  
  
  From the Context Window to External Cognition
&lt;/h2&gt;

&lt;p&gt;The starting point is almost brutal: the context window, by itself, is too fragile a memory to sustain long-running tasks.&lt;/p&gt;

&lt;p&gt;Even with very large contextual windows, the model suffers from imperfect compression, unstable salience, priority loss, and partial retrieval of goals.&lt;/p&gt;

&lt;p&gt;For this reason, Anthropic builds around Claude an external procedural memory composed of persistent scratchpads, task files, execution summaries, serialized checkpoints, and continuously updated state notes.&lt;/p&gt;

&lt;p&gt;In practice, the model is no longer forced to "remember everything", because it can reread what it has already established.&lt;/p&gt;

&lt;p&gt;This makes an enormous difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Harness as a System of Continuous Re-Anchoring
&lt;/h2&gt;

&lt;p&gt;In the classical paradigm, we tell the agent: continue.&lt;/p&gt;

&lt;p&gt;In the Anthropic paradigm, instead, we tell it: stop, reread where you are, summarize what you are doing, update your state, then continue.&lt;/p&gt;

&lt;p&gt;This creates a re-anchoring cycle.&lt;/p&gt;

&lt;p&gt;The agent is periodically brought back to the goal, to the progress already completed, to the constraints still open, and to the errors that have emerged.&lt;/p&gt;

&lt;p&gt;It is a form of "artificial continuity".&lt;/p&gt;

&lt;p&gt;Cognition is not allowed to flow in a monolithic way; it is broken apart, recorded, and reconsolidated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Agent Evaluation: Thinking Is Not Enough, You Need to Be Critiqued
&lt;/h2&gt;

&lt;p&gt;Another interesting aspect of Anthropic's work is the use of generator/evaluator structures: one agent produces, and a second agent evaluates quality, coherence, usability, and adherence to requirements.&lt;/p&gt;

&lt;p&gt;The result is not simply "more review".&lt;/p&gt;

&lt;p&gt;It is something subtler: verification stops being a final phase and becomes part of cognitive continuity itself.&lt;/p&gt;

&lt;p&gt;In this way, each evaluation prevents the primary agent from drifting too far away from the correct trajectory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Runtime Harness Is Not Meant to Make the Agent Act Better: It Is Meant to Make It Think Longer
&lt;/h2&gt;

&lt;p&gt;This is perhaps the most important point: while OpenAI builds above all a structural harness, Anthropic builds above all a temporal harness.&lt;/p&gt;

&lt;p&gt;The problem it is solving is no longer "how do I get Claude to generate good code?", but "how do I prevent Claude from losing the thread while it continues generating it?".&lt;/p&gt;

&lt;p&gt;It sounds like a nuance, but it completely changes the design, because here:&lt;/p&gt;

&lt;p&gt;the memory is an external artifact,&lt;br&gt;
planning is serialized,&lt;br&gt;
review is recurrent,&lt;br&gt;
the task is continuously re-anchored.&lt;/p&gt;

&lt;p&gt;So this is not only orchestration-it is assisted cognitive continuity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If OpenAI's repository harness teaches us that an agent needs to live inside a readable codebase, Anthropic reminds us that this is not enough.&lt;/p&gt;

&lt;p&gt;An agent may have perfect tools, perfect documentation, perfect constraints and still get lost if it is allowed to run for too long without an external memory that keeps it coherent.&lt;/p&gt;

&lt;p&gt;And this is where the runtime harness changes the game: it does not merely build an environment in which the agent can act; it builds an environment in which the agent can continue to know &lt;em&gt;why&lt;/em&gt; it is acting.&lt;/p&gt;

&lt;p&gt;In agent engineering, this may be the difference between episodic automation and real autonomy.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
    </item>
    <item>
      <title>OpenAI and the New Cognitive Architecture of Software Repositories</title>
      <dc:creator>eleonorarocchi</dc:creator>
      <pubDate>Tue, 28 Apr 2026 05:36:00 +0000</pubDate>
      <link>https://dev.to/eleonorarocchi/openai-and-the-new-cognitive-architecture-of-software-repositories-383m</link>
      <guid>https://dev.to/eleonorarocchi/openai-and-the-new-cognitive-architecture-of-software-repositories-383m</guid>
      <description>&lt;h1&gt;
  
  
  TL;DR
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI's latest harness engineering report suggests something deeper than "agents can write a lot of code."&lt;/li&gt;
&lt;li&gt;It suggests that the real bottleneck in agentic software is no longer just the model, but the repository itself.&lt;/li&gt;
&lt;li&gt;Once agents become primary executors, codebases must stop being designed only for human maintainers and start becoming semantically navigable computational environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  OpenAI and the Birth of the Repository Harness: When Code Must Become Readable to Agents
&lt;/h1&gt;

&lt;p&gt;Over the past few months, the concept of &lt;em&gt;harness engineering&lt;/em&gt; has become one of the most frequently discussed categories in AI engineering, especially as companies have started confronting a very simple problem: an agent may be brilliant in isolated executions, but without an environment intentionally designed around it, it quickly begins to generate entropy.&lt;/p&gt;

&lt;p&gt;As I discussed in my previous article,&lt;a href="https://dev.to/eleonorarocchi/harness-engineering-la-parte-piu-importante-degli-agenti-ai-4jnd"&gt;Harness Engineering: The Most Important Part of AI Agents&lt;/a&gt; harnesses represent the truly critical layer of an agentic system, and this infrastructure must evolve significantly when moving from prototype to production.&lt;br&gt;
The case recently published by &lt;a href="https://openai.com/it-IT/index/harness-engineering/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;, however, adds an even more important piece to the puzzle: it suggests that the first object we need to learn how to design for agents may not be the model itself, but the repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Number Everyone Quoted — and the One That Actually Matters
&lt;/h2&gt;

&lt;p&gt;In the report &lt;em&gt;Harness engineering: leveraging Codex in an agent-first world&lt;/em&gt;, OpenAI explains that it built a functional internal beta with roughly one million lines of code generated entirely by Codex, zero manually written lines, and more than 1,500 pull requests handled by an extremely small team.&lt;/p&gt;

&lt;p&gt;It is an impressive figure, and naturally it made headlines.&lt;br&gt;
But stopping at the quantity means missing the central point.&lt;/p&gt;

&lt;p&gt;The real message of the report is something else:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;productivity did not increase because Codex "writes code very fast";&lt;/li&gt;
&lt;li&gt;it increased because engineers stopped treating the repository as a simple container of files and started treating it as an environment computable by agents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, OpenAI did not simply use a coding agent inside a codebase: it transformed the codebase into something an agent can read, interpret, and correct reliably.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Human Codebase to Agent-Readable Codebase
&lt;/h2&gt;

&lt;p&gt;There are at least four very clear signals of this transformation.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Repository Knowledge Becomes the System of Record
&lt;/h3&gt;

&lt;p&gt;OpenAI insists on one precise point: the repository must contain the operational truth.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;versioned internal documentation;&lt;/li&gt;
&lt;li&gt;architectural maps;&lt;/li&gt;
&lt;li&gt;decision histories;&lt;/li&gt;
&lt;li&gt;files such as &lt;code&gt;AGENTS.md&lt;/code&gt; that function as a semantic entry point for agents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not about adding "more documentation," but about ensuring that the repository becomes machine-queryable memory, not merely something readable by humans.&lt;/p&gt;

&lt;p&gt;The agent should not have to infer structure from scattered code; it should be able to interrogate that structure directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. CI Stops Being Just Quality Assurance and Becomes a Runtime Training Mechanism
&lt;/h3&gt;

&lt;p&gt;Linting, formatting, boundary checks, import policies, automated verification: in a traditional pipeline these serve to maintain order, while in a repository harness they serve something more: they become deterministic feedback loops that continuously teach the agent which behaviors are allowed and which are not.&lt;/p&gt;

&lt;p&gt;The agent makes a mistake, CI blocks the execution, the log returns the reason, the task is iterated again: quality control stops being post-production and becomes part of the execution-time reasoning process.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Observability Is Designed for the Agent Too
&lt;/h3&gt;

&lt;p&gt;OpenAI explains that it invested heavily in structured logs, diagnostic traces, verifiable outputs, and inspection tools.&lt;/p&gt;

&lt;p&gt;This is because an agent that cannot properly read its own failures is forced to regenerate blindly; conversely, an agent with access to semantically dense error information can perform self-debugging.&lt;/p&gt;

&lt;p&gt;Observability, therefore, is no longer just a developer dashboard: it becomes a cognitive surface.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Developers Stop Being Authors of Code and Become Authors of Constraints
&lt;/h3&gt;

&lt;p&gt;This is perhaps the most interesting point in the entire OpenAI article: human work does not disappear, it shifts.&lt;/p&gt;

&lt;p&gt;Less time spent on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;direct implementation;&lt;/li&gt;
&lt;li&gt;manual fixes;&lt;/li&gt;
&lt;li&gt;tactical coding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More time spent on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;designing repository structure;&lt;/li&gt;
&lt;li&gt;defining architectural boundaries;&lt;/li&gt;
&lt;li&gt;building feedback loops;&lt;/li&gt;
&lt;li&gt;cleaning entropy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The engineer writes fewer and fewer features, and more and more conditions of intelligibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Repository Harness as the New Unit of Design
&lt;/h2&gt;

&lt;p&gt;If we look closely, the OpenAI case suggests a strong thesis: the first mature industrial harness is not simply a wrapper around the model; it is a codebase deliberately made readable to agents.&lt;/p&gt;

&lt;p&gt;And this is an important distinction.&lt;/p&gt;

&lt;p&gt;For years we assumed that the agent problem was primarily about improving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prompting;&lt;/li&gt;
&lt;li&gt;reasoning;&lt;/li&gt;
&lt;li&gt;tool use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OpenAI shows that there is an upstream layer beyond all of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a mediocre agent inside an agent-readable repository can still produce usable work;&lt;/li&gt;
&lt;li&gt;a highly capable agent inside an opaque repository will still produce entropy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bottleneck is not only the model, but increasingly the computability of the environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Perhaps OpenAI's most interesting contribution to the harness engineering debate is not having shown that software can be built with agents.&lt;/p&gt;

&lt;p&gt;It is having shown that, to do it seriously, we need to accept one uncomfortable fact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it is no longer enough for code to be maintainable by humans;&lt;/li&gt;
&lt;li&gt;it must become navigable, verifiable, and semantically readable by agents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this radically shifts the work of engineering.&lt;/p&gt;

&lt;p&gt;We are no longer designing only applications — we are (perhaps finally) beginning to design repositories that can be inhabited by non-deterministic intelligences.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
    </item>
    <item>
      <title>Building a Harness: From Prototype to Production</title>
      <dc:creator>eleonorarocchi</dc:creator>
      <pubDate>Fri, 24 Apr 2026 05:12:00 +0000</pubDate>
      <link>https://dev.to/eleonorarocchi/building-a-harness-from-prototype-to-production-o30</link>
      <guid>https://dev.to/eleonorarocchi/building-a-harness-from-prototype-to-production-o30</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An agent doesn’t truly work because of the model, but because of the harness controlling it.&lt;/li&gt;
&lt;li&gt;Moving from demo to production requires handling errors, state, memory, and observability.&lt;/li&gt;
&lt;li&gt;A well-designed harness reduces model unpredictability and shifts complexity into code, making the system reliable and usable in real-world scenarios.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;In article &lt;a href="https://dev.to/eleonorarocchi/harness-engineering-la-parte-piu-importante-degli-agenti-ai-4jnd"&gt;Harness Engineering: The Most Important Part of AI Agents&lt;/a&gt; we saw a fundamental point: the problem with agents isn't (only) the model, but the system around it.&lt;/p&gt;

&lt;p&gt;But what does it really mean to build that system?&lt;/p&gt;

&lt;h2&gt;
  
  
  The moment everything breaks
&lt;/h2&gt;

&lt;p&gt;There's a fairly universal phase: you've implemented a demo, it works well, the model responds, uses a tool, maybe even completes multi-step tasks, and everything looks promising.&lt;/p&gt;

&lt;p&gt;Then you try to use it in a real-world context, and the problems emerge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;invalid outputs&lt;/li&gt;
&lt;li&gt;incorrect API calls&lt;/li&gt;
&lt;li&gt;infinite loops&lt;/li&gt;
&lt;li&gt;loss of context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not that the model got worse—the system's complexity increased without having a harness solid enough to manage it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The harness as a control system
&lt;/h2&gt;

&lt;p&gt;It becomes clear that the harness isn't just a "container"—it's more like a control system designed to guide the model along a precise path, reducing its freedom when necessary and allowing it when useful.&lt;/p&gt;

&lt;p&gt;This is a delicate balance: too much control means loss of flexibility; too little control means loss of reliability.&lt;/p&gt;

&lt;p&gt;And this is where the real design work begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error handling becomes the main case
&lt;/h2&gt;

&lt;p&gt;In traditional software, errors are edge cases. Anyone with experience in agent-based systems knows that errors are the norm.&lt;/p&gt;

&lt;p&gt;The key idea, however, is that a well-designed harness does not assume everything will go well—quite the opposite.&lt;/p&gt;

&lt;p&gt;It therefore introduces mechanisms such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validating outputs before using them&lt;/li&gt;
&lt;li&gt;retrying when something goes wrong&lt;/li&gt;
&lt;li&gt;falling back to alternative paths&lt;/li&gt;
&lt;li&gt;controlled interruption of loops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what makes the system usable.&lt;/p&gt;

&lt;h2&gt;
  
  
  State and memory: the invisible problem
&lt;/h2&gt;

&lt;p&gt;Another issue that emerges very early is state management: an agent without memory is little more than a stateless function—but adding memory introduces complexity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what to store&lt;/li&gt;
&lt;li&gt;for how long&lt;/li&gt;
&lt;li&gt;how to update the state&lt;/li&gt;
&lt;li&gt;what happens when it becomes inconsistent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These decisions must be made when structuring the harness.&lt;/p&gt;

&lt;p&gt;And it's precisely here that many subtle bugs tend to arise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability: knowing what's happening
&lt;/h2&gt;

&lt;p&gt;When something goes wrong (and sooner or later it will), the important question is:&lt;/p&gt;

&lt;p&gt;"Can I understand what happened?"&lt;/p&gt;

&lt;p&gt;Without logging and tracing, working with agents becomes almost impossible.&lt;/p&gt;

&lt;p&gt;Because you need to see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every step of the reasoning&lt;/li&gt;
&lt;li&gt;every tool call&lt;/li&gt;
&lt;li&gt;every output transformation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And not just for debugging, but to evolve the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving complexity to the right place
&lt;/h2&gt;

&lt;p&gt;An interesting aspect is that, as you improve the harness, the system becomes more predictable—even without changing the model.&lt;/p&gt;

&lt;p&gt;This happens because complexity is being moved out of an "opaque" component (the model) and into code that can actually be controlled.&lt;/p&gt;

&lt;p&gt;It's a shift in strategy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;less blind trust in the model&lt;/li&gt;
&lt;li&gt;more explicit control in the system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which, ultimately, is software engineering.&lt;/p&gt;

&lt;p&gt;In fact, we can say that building agents today is much closer to traditional software engineering than it might seem.&lt;/p&gt;

&lt;p&gt;There are flows, states, error handling, integrations, observability…&lt;/p&gt;

&lt;p&gt;The only difference is that instead of deterministic functions, there's a probabilistic model.&lt;/p&gt;

&lt;p&gt;The harness is what holds everything together—and that's what makes the difference between something that only works in a demo and something that truly works in production.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
    </item>
    <item>
      <title>Harness Engineering: The Most Important Part of AI Agents</title>
      <dc:creator>eleonorarocchi</dc:creator>
      <pubDate>Tue, 21 Apr 2026 05:25:00 +0000</pubDate>
      <link>https://dev.to/eleonorarocchi/harness-engineering-la-parte-piu-importante-degli-agenti-ai-4jnd</link>
      <guid>https://dev.to/eleonorarocchi/harness-engineering-la-parte-piu-importante-degli-agenti-ai-4jnd</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;LLMs don’t become agents because they’re more intelligent, but because we place them inside a system that makes them usable.&lt;/li&gt;
&lt;li&gt;That system - which handles context, tools, errors, and flows - is the harness.&lt;/li&gt;
&lt;li&gt;If an agent doesn’t work, the problem is most likely not the model, but everything you’ve built around it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The agent isn’t broken. Your harness is.
&lt;/h2&gt;

&lt;p&gt;In recent years, we’ve seen an impressive acceleration in the world of language models. Every month (every day!) something more powerful, more efficient, more “intelligent” comes out. And inevitably, the conversation always focuses there: which model to use, how many parameters it has, how well it performs on benchmarks.&lt;/p&gt;

&lt;p&gt;But when you try to build something real, something interesting happens: the model stops being the main problem.&lt;/p&gt;

&lt;p&gt;When you move from a demo to a system that actually has to work (with real users, messy data, unpredictable edge cases), you realize that the LLM alone isn’t enough. Not because it isn’t powerful enough, but because it isn’t designed to be reliable.&lt;/p&gt;

&lt;p&gt;This is where what’s called &lt;strong&gt;harness engineering&lt;/strong&gt; comes into play.&lt;/p&gt;

&lt;h2&gt;
  
  
  It’s not about the model, it’s about the system
&lt;/h2&gt;

&lt;p&gt;There’s a concept that comes up often lately: agent = model + harness.&lt;/p&gt;

&lt;p&gt;It sounds like a simplification, but it’s actually a very accurate description of what happens in practice.&lt;/p&gt;

&lt;p&gt;The model generates text. The harness decides what that text means, what to do with it, when to trust it, and when not to.&lt;/p&gt;

&lt;p&gt;It’s a subtle distinction, but it completely changes the way you design a system.&lt;/p&gt;

&lt;p&gt;Because the moment you start building an agent, you are implicitly also building a way to manage context, call external tools, verify that the output makes sense, and recover when something goes wrong.&lt;/p&gt;

&lt;p&gt;And none of that lives inside the model. It lives around the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The “strange” behavior of LLMs
&lt;/h2&gt;

&lt;p&gt;Anyone who has worked even a little with these systems has already seen the problem.&lt;/p&gt;

&lt;p&gt;Same prompt, same input, two different outputs.&lt;br&gt;
Or: it works perfectly for ten requests, and then fails on something trivial.&lt;/p&gt;

&lt;p&gt;That’s not a bug. It’s the nature of the model.&lt;/p&gt;

&lt;p&gt;LLMs are not deterministic systems designed to be 100% reliable. They are excellent at generalizing, less so at guaranteeing consistency.&lt;/p&gt;

&lt;p&gt;And this is where the developer’s role changes.&lt;/p&gt;

&lt;p&gt;You’re no longer writing code that &lt;em&gt;does things&lt;/em&gt;.&lt;br&gt;
You’re building a system that manages an unreliable component.&lt;/p&gt;

&lt;p&gt;And &lt;strong&gt;that system is the harness&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  From prompt engineering to system design
&lt;/h2&gt;

&lt;p&gt;For a long time, we treated these problems as an extension of prompt engineering.&lt;/p&gt;

&lt;p&gt;“Let’s write a better prompt.”&lt;/p&gt;

&lt;p&gt;That works, up to a point.&lt;/p&gt;

&lt;p&gt;Then you start adding automatic retries, structured parsing, output validation, memory between steps.&lt;/p&gt;

&lt;p&gt;And without realizing it, you’re no longer working on a prompt — you’re designing a system.&lt;/p&gt;

&lt;p&gt;This is probably the most important transition: moving from thinking in terms of input/output to thinking in terms of flows, states, and controls.&lt;/p&gt;

&lt;h2&gt;
  
  
  The harness as a translator between model and reality
&lt;/h2&gt;

&lt;p&gt;A useful way to think about the harness is as a translation layer.&lt;/p&gt;

&lt;p&gt;On one side, the model: operating in natural language, probabilistic, flexible.&lt;/p&gt;

&lt;p&gt;On the other side, the real world: APIs that break, incomplete data, rigid formats, irreversible actions.&lt;/p&gt;

&lt;p&gt;The harness sits in between and acts as a mediator.&lt;/p&gt;

&lt;p&gt;It takes something “soft” (the model’s text) and turns it into something “hard” (concrete actions).&lt;br&gt;
And it also does the reverse: it takes structured signals and makes them usable for the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why two agents using the same model behave differently
&lt;/h2&gt;

&lt;p&gt;Let’s say we have two applications using the exact same LLM and getting completely different results.&lt;/p&gt;

&lt;p&gt;At first, it seems strange.&lt;/p&gt;

&lt;p&gt;But looking closer, you realize the difference isn’t in the model. It’s in everything around it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How context is managed&lt;/li&gt;
&lt;li&gt;When tools are called&lt;/li&gt;
&lt;li&gt;What happens when something fails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words: the harness.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
    </item>
    <item>
      <title>Local LLM with Google Gemma: On-Device Inference Between Theory and Practice</title>
      <dc:creator>eleonorarocchi</dc:creator>
      <pubDate>Fri, 17 Apr 2026 06:30:00 +0000</pubDate>
      <link>https://dev.to/eleonorarocchi/local-llm-with-google-gemma-on-device-inference-between-theory-and-practice-4lbn</link>
      <guid>https://dev.to/eleonorarocchi/local-llm-with-google-gemma-on-device-inference-between-theory-and-practice-4lbn</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Running an LLM locally on a smartphone is now possible—and it’s not even that exotic anymore. The interesting part is no longer &lt;em&gt;whether&lt;/em&gt; it can be done, but &lt;em&gt;how&lt;/em&gt; it’s done and what trade-offs actually emerge: model format, runtime, performance, and distribution.&lt;/p&gt;

&lt;p&gt;To understand this better, I built a small Flutter app that performs on-device inference using LiteRT-LM and a Gemma 4 E2B model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Starting Point
&lt;/h2&gt;

&lt;p&gt;Anyone working with LLMs already knows: local inference isn’t new. Between quantization, smaller models, and optimized runtimes, running models directly on devices is a real path.&lt;/p&gt;

&lt;p&gt;So the interesting question today is no longer “can it be done?”, but rather: &lt;strong&gt;what does this integration actually look like when you bring it to mobile?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To answer that, I chose a deliberately simple setup: a Flutter app, a textarea, a button, and a response generated locally by the model. No backend, no API, no remote calls. Just the app and the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why LiteRT-LM
&lt;/h2&gt;

&lt;p&gt;It’s worth pausing here, because the runtime significantly changes the kind of work you’re doing.&lt;/p&gt;

&lt;p&gt;LiteRT-LM is not the only option for on-device inference. In the mobile local-model landscape, alternatives like llama.cpp (with GGUF models, widely used for quantized LLMs), ONNX Runtime (more focused on cross-platform portability), and ExecuTorch (the mobile runtime from the PyTorch ecosystem, still maturing) offer different approaches depending on the model type and target hardware.&lt;/p&gt;

&lt;p&gt;The main advantage of LiteRT-LM, however, is its native integration with the Android ecosystem and direct support for hardware delegates like the device’s GPU and NPU, making it the most straightforward choice for on-device inference without dealing with format conversions or external dependencies.&lt;/p&gt;

&lt;p&gt;That said, there is a trade-off: the approach is less flexible than others. You can’t just use “any” model on the fly—you either use models already prepared for LiteRT or handle the conversion yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Gemma 4 E2B
&lt;/h2&gt;

&lt;p&gt;For the model, I used this variant:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The choice is not random. The Gemma 4 family includes different variants designed to balance capability and computational requirements. The &lt;strong&gt;E2B&lt;/strong&gt; version is interesting because it sits at a sensible middle ground: it’s not the largest model in the family (far from it), but it’s capable enough to produce useful output while still being compact enough to make sense on a smartphone.&lt;/p&gt;

&lt;p&gt;In other words: it’s a practical choice—not because it’s “the best ever,” but because it represents the kind of compromise that makes sense when constraints include not just output quality, but also memory, loading time, and inference speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Thing You Notice: Size
&lt;/h2&gt;

&lt;p&gt;The file you download from Hugging Face weighs about &lt;strong&gt;2.4 GB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That’s not automatically a deal-breaker. Today, app stores and distribution systems offer various strategies for handling large assets: dynamic downloads, splits, additional modules, local caching...&lt;/p&gt;

&lt;p&gt;Still, it’s important to be aware of this when thinking about production, because you’ll definitely need to reason concretely about how to package and distribute your app.&lt;/p&gt;

&lt;p&gt;For a simple experiment like this, the easiest approach is to include the model in the app assets and then copy it to the local filesystem on first launch.&lt;/p&gt;

&lt;p&gt;If you’re wondering why the model needs to be copied to the local filesystem, the reason is simple: LiteRT-LM (and many ML runtimes in general) require a file path on disk because they need direct access to the model file. During inference, the runtime constantly jumps between different parts of the model and accesses specific blocks (layers, weights, cache), often reusing data or working in parallel. This requires fast random access. Also, the model is not fully loaded into memory but memory-mapped as needed. None of this is feasible with a stream from assets, which only provides sequential access.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Step-by-Step Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create the Flutter project
&lt;/h3&gt;

&lt;p&gt;From the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter create edge_llm_app
&lt;span class="nb"&gt;cd &lt;/span&gt;edge_llm_app
flutter run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, you’ll see the classic default Flutter app with the counter.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Add LiteRT-LM to the Android project
&lt;/h3&gt;

&lt;p&gt;This step adds the Android runtime required to run the model on-device.&lt;/p&gt;

&lt;p&gt;Open the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;android/app/build.gradle.kts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there’s no &lt;code&gt;dependencies&lt;/code&gt; block, you can add one at the end of the file. Inside it, insert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.google.ai.edge.litertlm:litertlm-android:latest.release"&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;h3&gt;
  
  
  3. Enable the native library for GPU backend
&lt;/h3&gt;

&lt;p&gt;To use the GPU (and other accelerators) for general-purpose computation—not graphics—you use &lt;strong&gt;OpenCL&lt;/strong&gt;. In this case, it’s needed to run heavy computations like those of language models. Of course, this only works if the device supports it.&lt;/p&gt;

&lt;p&gt;Open the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;android/app/src/main/AndroidManifest.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find the &lt;code&gt;&amp;lt;application&amp;gt;&lt;/code&gt; tag and add this line inside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;uses-native-library&lt;/span&gt;
    &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"libOpenCL.so"&lt;/span&gt;
    &lt;span class="na"&gt;android:required=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the app to use OpenCL if the device supports it.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Download the model
&lt;/h3&gt;

&lt;p&gt;Download the &lt;code&gt;.litertlm&lt;/code&gt; file from:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;Files and versions&lt;/strong&gt; tab, you’ll find the model file. For simplicity, you can rename it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gemma.litertlm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Copy the model into the right folder
&lt;/h3&gt;

&lt;p&gt;Create the assets folder if it doesn’t exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;android/app/src/main/assets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then place the downloaded file inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;android/app/src/main/assets/gemma.litertlm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Create the Flutter ↔ Android bridge
&lt;/h3&gt;

&lt;p&gt;In the Flutter project, create this file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lib/llm_service.dart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And paste this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/services.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LlmService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MethodChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'llm'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_channel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'init'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&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;_channel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'ask'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;'prompt'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&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 file is the bridge between the Flutter UI and the native Android code that will actually run the model.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Modify &lt;code&gt;MainActivity.kt&lt;/code&gt;
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;android/app/src/main/kotlin/com/example/edge_llm_app/MainActivity.kt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The exact path may vary slightly depending on your package name.)&lt;/p&gt;

&lt;p&gt;Replace the content with a version that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;initializes the engine&lt;/li&gt;
&lt;li&gt;copies the model from assets&lt;/li&gt;
&lt;li&gt;exposes two methods to Flutter: &lt;code&gt;init&lt;/code&gt; and &lt;code&gt;ask&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// (code unchanged)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the core of the integration. The model is copied from assets to the filesystem, the runtime is initialized, and the prompt is passed to the model.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Replace the default Flutter UI
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lib/main.dart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace its content with something simple but usable, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// (code unchanged)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, you have a minimal UI that’s sufficient to test inference.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Run the app on your phone
&lt;/h3&gt;

&lt;p&gt;Now you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where you see the difference compared to an API call.&lt;/p&gt;

&lt;p&gt;When you press “Send,” the phone does the work. The UI may freeze for a few seconds, then the response arrives (the UI can definitely be improved, but that’s not the goal here).&lt;/p&gt;

&lt;p&gt;From the logs, you can clearly see the different phases of inference: prefill, generation, output.&lt;/p&gt;

&lt;p&gt;And most importantly: everything happens locally!&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Exercise Really Shows
&lt;/h2&gt;

&lt;p&gt;In the end, the interesting point is not proving that you can run an LLM on a phone. That’s already established.&lt;/p&gt;

&lt;p&gt;The real insight is understanding &lt;strong&gt;what kind of integration you are building&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;LiteRT-LM simplifies execution on mobile but requires you to accept a specific ecosystem. Gemma 4 E2B makes sense because it sits in a realistic range for this type of use. And the model size is not so much an absolute deal-breaker as it is an architectural variable you need to manage.&lt;/p&gt;

&lt;p&gt;The biggest difference, however, is conceptual: when working with APIs, AI is an external service. Here, it becomes part of the application itself. You start reasoning in terms of filesystem, memory, initialization time, hardware, and acceleration.&lt;/p&gt;

&lt;p&gt;You’re no longer just making a request.&lt;/p&gt;

&lt;p&gt;You’re executing something locally.&lt;/p&gt;

&lt;p&gt;And that’s the most interesting paradigm shift of all.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gemini</category>
      <category>llm</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
