<?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: Machina Tools</title>
    <description>The latest articles on DEV Community by Machina Tools (@machina_tools).</description>
    <link>https://dev.to/machina_tools</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%2F3977721%2F847cddd7-78c6-406c-b1c2-db5ed825b735.png</url>
      <title>DEV Community: Machina Tools</title>
      <link>https://dev.to/machina_tools</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/machina_tools"/>
    <language>en</language>
    <item>
      <title>Steering Vectors: Changing What an LLM Wants Without Touching Its Weights</title>
      <dc:creator>Machina Tools</dc:creator>
      <pubDate>Sun, 28 Jun 2026 22:56:24 +0000</pubDate>
      <link>https://dev.to/machina_tools/steering-vectors-changing-what-an-llm-wants-without-touching-its-weights-3okg</link>
      <guid>https://dev.to/machina_tools/steering-vectors-changing-what-an-llm-wants-without-touching-its-weights-3okg</guid>
      <description>&lt;p&gt;LLMs encode concepts as geometric directions in activation space. You can find those directions, add them at inference time, and shift model behavior - without touching a single weight.&lt;/p&gt;

&lt;p&gt;This is called &lt;strong&gt;steering vectors&lt;/strong&gt;, and it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;A language model's residual stream is a high-dimensional vector that accumulates information as it passes through layers. The linear representation hypothesis says that concepts like "pessimism," "formality," or "Python expertise" correspond to specific directions in this space.&lt;/p&gt;

&lt;p&gt;If that's true, you should be able to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the direction that encodes a concept (using contrastive examples)&lt;/li&gt;
&lt;li&gt;Add a scaled version of that direction at a specific layer&lt;/li&gt;
&lt;li&gt;Observe the model behaving more "conceptfully"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And you can.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extracting a steering vector
&lt;/h2&gt;

&lt;p&gt;The simplest method: take sentence pairs that differ only in the target concept, run them through the model, and average the difference in activations at a chosen layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;transformer_lens&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HookedTransformer&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_steering_vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;positive_prompts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;negative_prompts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Extract direction that points from negative to positive concept.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;pos_acts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;neg_acts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;positive_prompts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_with_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;pos_acts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blocks.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.hook_resid_post&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;negative_prompts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_with_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;neg_acts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blocks.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.hook_resid_post&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos_acts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;neg_acts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Applying it at inference
&lt;/h2&gt;

&lt;p&gt;Once you have the vector, inject it via a hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;steering_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;20.0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hook_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hook&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;value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;steering_vector&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hook_fn&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_steered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;steering_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;20.0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;hook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;steering_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;hook_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blocks.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.hook_resid_post&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fwd_hooks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;hook_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;)]):&lt;/span&gt;
        &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_new_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&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;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What you can steer
&lt;/h2&gt;

&lt;p&gt;The same technique works for a surprising range of properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pessimism/optimism&lt;/strong&gt; - shifts narrative tone measurably&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python enthusiasm&lt;/strong&gt; - makes the model reach for Python examples&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Formality&lt;/strong&gt; - shifts register from casual to professional&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sycophancy&lt;/strong&gt; - can be used to &lt;em&gt;reduce&lt;/em&gt; agreement-seeking behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key finding from Turner et al. (2023) and Zou et al. (2023): these vectors generalize. A pessimism vector extracted from weather sentences transfers to unrelated topics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking if a concept is linearly encoded
&lt;/h2&gt;

&lt;p&gt;Before steering, you can verify the concept is actually linearly represented using a probe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.linear_model&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LogisticRegression&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;probe_concept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;positive_prompts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;negative_prompts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;positive_prompts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_with_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blocks.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.hook_resid_post&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;numpy&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;negative_prompts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_with_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blocks.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.hook_resid_post&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;numpy&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;probe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LogisticRegression&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&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;probe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# accuracy &amp;gt; 0.9 = concept is linearly encoded
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;High probe accuracy means the concept is cleanly linearly separable in activation space - and steering should work well.&lt;/p&gt;




&lt;p&gt;Full writeup with more experiments and analysis on the Machina blog: &lt;a href="https://machina.chat/blog/posts/steering-vectors-control-llm-activations" rel="noopener noreferrer"&gt;Steering Vectors: Changing What an LLM Wants Without Touching Its Weights&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at machina.chat&lt;/em&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>ai</category>
      <category>python</category>
      <category>deeplearning</category>
    </item>
    <item>
      <title>How I Built PromptBoard — A Visual Canvas for Building AI Prompts</title>
      <dc:creator>Machina Tools</dc:creator>
      <pubDate>Sun, 21 Jun 2026 02:14:00 +0000</pubDate>
      <link>https://dev.to/machina_tools/how-i-built-promptboard-a-visual-canvas-for-building-ai-prompts-442o</link>
      <guid>https://dev.to/machina_tools/how-i-built-promptboard-a-visual-canvas-for-building-ai-prompts-442o</guid>
      <description>&lt;p&gt;There's a class of AI prompts that don't fit in a text box.&lt;/p&gt;

&lt;p&gt;Not because the ideas are too long — you can always write more. The problem is that the &lt;em&gt;structure&lt;/em&gt; of what you want to communicate is inherently visual. You're describing a flow. You're pointing at an image. You're listing constraints that apply to some parts of the context but not others. You're trying to give the AI a briefing, not a paragraph.&lt;/p&gt;

&lt;p&gt;The text box forces everything into one dimension. And the AI, however capable, has to reconstruct the structure you had in your head from a flat string of text.&lt;/p&gt;

&lt;p&gt;PromptBoard solves this by flipping the approach: you build the prompt visually first, then export it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with prompting complex tasks
&lt;/h2&gt;

&lt;p&gt;Every developer who uses AI agents regularly hits a pattern like this:&lt;/p&gt;

&lt;p&gt;You have a bug to fix. It's not a simple bug — it involves a flow you need to explain, a screenshot of the broken state, three or four constraints the fix has to respect, and a description of what the correct behavior should look like.&lt;/p&gt;

&lt;p&gt;You start typing. You write the task description, then realize you need to explain the flow first. You paste in a screenshot and then write around it. You add the constraints at the end but they're not clearly linked to the specific parts they apply to. By the time you hit send, the prompt is a 400-word wall of text with an image in the middle.&lt;/p&gt;

&lt;p&gt;The AI can often handle this. But you're asking it to do structural inference that you could have done once, clearly, in a canvas.&lt;/p&gt;

&lt;p&gt;The deeper issue is that prompts have a &lt;strong&gt;natural graph structure&lt;/strong&gt;: nodes (concepts, constraints, examples) with labeled relationships between them. A text box serializes that graph into a linear sequence and throws away the relationship labels.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design: blocks, arrows, export
&lt;/h2&gt;

&lt;p&gt;PromptBoard is built around three concepts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blocks&lt;/strong&gt; are the nodes. There are three types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Text&lt;/strong&gt; — free-form content, the main carrier of context. Can have an optional label.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image&lt;/strong&gt; — drag-and-drop or paste a screenshot. Gets embedded as base64 in the export.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flow&lt;/strong&gt; — a process/decision/terminal node for describing logic visually.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Arrows&lt;/strong&gt; connect blocks and carry a label. "This constraint applies to this flow step." "This screenshot is evidence for this bug description." The relationships are explicit, not inferred from reading order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Export&lt;/strong&gt; serializes the canvas back to text — structured text. Blocks are rendered in top-to-bottom, left-to-right order. Arrows become a &lt;code&gt;## Flow&lt;/code&gt; section listing every connection with its label. Images are embedded as base64. The output is a Markdown file any AI can parse immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a canvas, not a form
&lt;/h2&gt;

&lt;p&gt;The first version of this tool was a form. Title field, description field, constraints field, image upload. Structured, explicit, readable.&lt;/p&gt;

&lt;p&gt;It was unusable.&lt;/p&gt;

&lt;p&gt;The problem with forms is that they impose a fixed schema. Your context doesn't always have a title and a description and three constraints. Sometimes it's just two things that are connected. Sometimes you have five images and no text yet.&lt;/p&gt;

&lt;p&gt;A canvas has no schema. You start with an empty surface and put things where they make sense. The structure emerges from the layout, not from a pre-defined form. That's exactly how you think through a problem before you explain it — spatially, not linearly.&lt;/p&gt;

&lt;h2&gt;
  
  
  How voice dictation works
&lt;/h2&gt;

&lt;p&gt;PromptBoard has voice dictation on every text block and arrow label. Two modes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chromium (Chrome, Edge):&lt;/strong&gt; uses the Web Speech API with &lt;code&gt;continuous: true&lt;/code&gt;. You click the mic button, talk, and transcribed text appends to the block in real time. No server, no API, no latency — the model runs in the browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firefox and others:&lt;/strong&gt; MediaRecorder captures the audio, then sends it to a local Whisper server (Transcriber, port 4324) for transcription. If Transcriber isn't running, a dialog appears — you can type what you said, or replay the audio.&lt;/p&gt;

&lt;p&gt;The asymmetry is intentional: Chromium's built-in speech recognition is good enough for note-taking velocity. Whisper is better for longer or more technical dictation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The export format
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Fix the checkout form&lt;/span&gt;

&lt;span class="gs"&gt;**Goal**&lt;/span&gt;
Fix the checkout form — Cart component won't submit after the last refactor

&lt;span class="gs"&gt;**Constraints**&lt;/span&gt;
No new deps · TypeScript strict · keep under 50 lines

&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;cart-screenshot&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;data:image/png;base64,...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="gs"&gt;**[▭ Process]**&lt;/span&gt; Cart validates form fields

&lt;span class="gs"&gt;**[◇ Decision]**&lt;/span&gt; Payment API responds?

&lt;span class="gs"&gt;**[○ Terminal]**&lt;/span&gt; Show success or error state

&lt;span class="gu"&gt;## Flow&lt;/span&gt;

Cart validates form fields → Payment API responds? (calls POST /api/checkout)
Payment API responds? → Show success or error state (on failure: surface error message)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the AI reads this, it has: the task in plain language, constraints explicitly stated, the screenshot as direct visual evidence, and the flow as a labeled graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical architecture
&lt;/h2&gt;

&lt;p&gt;PromptBoard is a single HTML file, around 1,100 lines. No build step, no &lt;code&gt;npm install&lt;/code&gt;, no server. Open it in a browser and it works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State:&lt;/strong&gt; a single &lt;code&gt;S&lt;/code&gt; object holds all blocks, arrows, history stack, and interaction state. Everything is JSON-serializable. Boards are saved to &lt;code&gt;localStorage&lt;/code&gt; (up to 20 boards).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Undo/redo:&lt;/strong&gt; snapshot-based history (&lt;code&gt;JSON.stringify&lt;/code&gt; + &lt;code&gt;JSON.parse&lt;/code&gt; of the state). Up to 60 snapshots. &lt;code&gt;Ctrl+Z&lt;/code&gt; / &lt;code&gt;Ctrl+Y&lt;/code&gt; work everywhere outside a text input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arrows:&lt;/strong&gt; rendered as SVG quadratic Bézier curves with a slight perpendicular offset to avoid overlapping block edges. Hit areas are 14px-wide transparent paths over 1.5px visible paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Canvas:&lt;/strong&gt; 3000×2000px scrollable area. Blocks are &lt;code&gt;position:absolute&lt;/code&gt; divs. Drag uses &lt;code&gt;mousedown&lt;/code&gt; on the header + &lt;code&gt;mousemove&lt;/code&gt; + &lt;code&gt;mouseup&lt;/code&gt; on &lt;code&gt;document&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strengths
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No installation.&lt;/strong&gt; The tool lives in one file. Put it on a USB drive, serve it from any static host, or just keep it in your project folder and open it with a double-click.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multimodal output.&lt;/strong&gt; The base64 image embedding means the exported &lt;code&gt;.md&lt;/code&gt; is self-contained — images travel with the text. Paste the entire thing into Claude or GPT-4o and the screenshots are right there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Voice-first friendly.&lt;/strong&gt; For developers who think faster than they type, or who are debugging a live environment and need both hands, voice dictation makes PromptBoard usable without touching a keyboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Composable with the rest of Machina.&lt;/strong&gt; The &lt;code&gt;.md&lt;/code&gt; export is the same format BugCapture produces. A natural workflow: BugCapture records the bug, ContextForge adds the git diff and logs, PromptBoard adds the visual structure and constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;PromptBoard is part of &lt;a href="https://machina.chat" rel="noopener noreferrer"&gt;Machina&lt;/a&gt; — a free, open-source suite of AI developer tools.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/machina-tools/machina.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open &lt;code&gt;tools/promptboard/index.html&lt;/code&gt; in your browser. No server needed.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://github.com/machina-tools/machina/tree/main/tools/promptboard" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://machina.chat" rel="noopener noreferrer"&gt;machina.chat&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devtools</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>How I Built LearnBoard — The UI That Makes Your AI Remember You</title>
      <dc:creator>Machina Tools</dc:creator>
      <pubDate>Sun, 21 Jun 2026 02:13:01 +0000</pubDate>
      <link>https://dev.to/machina_tools/how-i-built-learnboard-the-ui-that-makes-your-ai-remember-you-3413</link>
      <guid>https://dev.to/machina_tools/how-i-built-learnboard-the-ui-that-makes-your-ai-remember-you-3413</guid>
      <description>&lt;p&gt;There's a problem that every developer who works with AI agents eventually runs into: &lt;strong&gt;the AI doesn't remember you.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You spend twenty minutes at the start of every session re-explaining your stack, your preferences, the constraint you discovered last week, the mistake you almost made twice. You know you have to do this. The AI has no memory of your last conversation. Every session, it starts fresh.&lt;/p&gt;

&lt;p&gt;This is the biggest hidden cost of working with AI agents. It's not the hallucinations or the wrong answers — those are visible failures you can debug. The bigger cost is the invisible overhead: the context-building you do every single time, the lessons that get re-learned, the preferences that get ignored, the mistakes that happen again because the AI didn't know they were mistakes.&lt;/p&gt;

&lt;p&gt;LearnBoard is the tool I built to solve this.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea: structured memory as a file
&lt;/h2&gt;

&lt;p&gt;The insight that made this possible is simple: if you want an AI to remember something persistently, put it in a file it reads at session start.&lt;/p&gt;

&lt;p&gt;This isn't a new idea. CLAUDE.md works this way. Many productivity workflows work this way. But the gap was always the &lt;em&gt;management layer&lt;/em&gt; — there was no way to see what was in the memory, search it, or edit it without opening a raw text editor and hoping you understood the schema.&lt;/p&gt;

&lt;p&gt;LearnBoard is the management interface for a structured memory file called &lt;code&gt;LEARNING.md&lt;/code&gt;. Everything the AI has learned about you — your workflow preferences, patterns it has recognized, mistakes to avoid, successful approaches to revisit — lives in that file. LearnBoard makes that invisible layer visible, searchable, and editable in real time.&lt;/p&gt;

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

&lt;p&gt;LearnBoard is a Node.js server (port 4331) that serves a web dashboard for your &lt;code&gt;LEARNING.md&lt;/code&gt; file. The file uses a structured Markdown format with defined sections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lessons&lt;/strong&gt; — explicit things the AI has learned ("always prefer local tools over cloud APIs")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt; — the tools and versions in your environment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suggestions&lt;/strong&gt; — pending ideas from the AI that haven't been implemented yet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stats&lt;/strong&gt; — success rates, session counts, learning velocity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server watches the file with &lt;code&gt;chokidar&lt;/code&gt; and pushes updates to the UI over Server-Sent Events — so when you open a second terminal and the AI writes to the file, you see it appear in the dashboard in real time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;LEARNING.md excerpt:

&lt;span class="gu"&gt;## Lessons Learned&lt;/span&gt;

| # | Category | Lesson | Confidence | Sessions |
|---|----------|--------|------------|---------|
| 15 | tooling | Always prefer local/free solutions — never propose paid APIs without exhausting local alternatives first | high | 12 |
| 18 | ux | User prefers autonomous tools that find context on their own — not manual forms to fill in | high | 8 |
| 23 | ops | Always restart via the bash script that rebuilds nvm — &lt;span class="sb"&gt;`nohup node`&lt;/span&gt; fails silently without nvm env | confirmed | 5 |

&lt;span class="gu"&gt;## Pending Suggestions&lt;/span&gt;

| # | Suggestion | Status | Votes |
|---|-----------|--------|-------|
| 4 | Add keyboard shortcut to export BugCapture without clicking | pending | +3 |
| 7 | Auto-detect project from git remote in ContextForge | in-review | +2 |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That file, prefixed to the AI's system prompt, means the agent starts every session already knowing your preferences, your environment, and what approaches have worked or failed before.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dashboard
&lt;/h2&gt;

&lt;p&gt;The web UI has four main views:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lessons table&lt;/strong&gt; — all lessons with confidence score (low / medium / high / confirmed), category filter, free-text search, and inline editing. Click any cell to edit. New lessons append instantly. The AI can add lessons via a CLI flag; you see them appear in real time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools inventory&lt;/strong&gt; — your stack: language versions, frameworks, key dependencies. LearnBoard reads your &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;.nvmrc&lt;/code&gt;, and SSH environment automatically to bootstrap this section.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Suggestions queue&lt;/strong&gt; — pending ideas from the AI, with a +1/−1 voting system. Ideas that accumulate positive votes get promoted to the "accepted" column, which the AI treats as confirmed guidelines for future sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session stats&lt;/strong&gt; — a lightweight histogram of session count, fix rate, and learning velocity over time. You can see that lesson #15 has appeared in 12 sessions and has a 94% success rate. The AI isn't just guessing — it has evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The innovation: meta-AI
&lt;/h2&gt;

&lt;p&gt;The thing that makes LearnBoard different from a personal wiki or a note-taking tool is that it's &lt;strong&gt;designed to be read by the AI, not by you&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The structured format is chosen specifically to be unambiguous to a language model. The confidence scores, session counts, and vote history are signals the AI uses to weight lessons against each other. When two lessons conflict, the one with more sessions and higher confidence wins.&lt;/p&gt;

&lt;p&gt;The AI can also &lt;em&gt;write&lt;/em&gt; to the file. After a successful session, you can ask Claude or Copilot to "add a lesson to LEARNING.md about what we just discovered." It knows the schema, writes to the right section, and the dashboard updates immediately.&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;learning loop&lt;/strong&gt;: the AI learns from each session, stores the lesson, and is better informed for the next one. LearnBoard makes that loop visible and controllable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key strengths
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Full local operation.&lt;/strong&gt; No cloud, no sync, no account. &lt;code&gt;LEARNING.md&lt;/code&gt; is a plain text file you can read, commit, backup, and share without any vendor dependency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI-agnostic.&lt;/strong&gt; The file format works with Claude, Copilot, GPT-4, Gemini, or any agent that accepts system prompt context. You're not locked into a specific tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human-readable fallback.&lt;/strong&gt; When no dashboard is running, the memory layer is just a Markdown file. No black box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Survivable architecture.&lt;/strong&gt; When a new AI model comes out, you don't migrate data. The file stays the same. The new model reads the same lessons on day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technical stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;Node.js ESM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File watching&lt;/td&gt;
&lt;td&gt;&lt;code&gt;chokidar&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Markdown parsing&lt;/td&gt;
&lt;td&gt;&lt;code&gt;marked&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time updates&lt;/td&gt;
&lt;td&gt;Server-Sent Events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Vanilla JavaScript, no framework&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The server starts in under a second and uses less than 50MB of RAM.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real test
&lt;/h2&gt;

&lt;p&gt;I've been running LearnBoard on every project for four months. My &lt;code&gt;LEARNING.md&lt;/code&gt; file has grown to 34 lessons, 18 pending suggestions, and a tools inventory for 6 active projects.&lt;/p&gt;

&lt;p&gt;The sessions where I don't preload the context are noticeably worse. The AI proposes solutions I've already ruled out, asks questions I've already answered, and sometimes makes the exact mistakes that are documented in the file.&lt;/p&gt;

&lt;p&gt;The most concrete evidence: lesson #19 documents a deployment pattern specific to one client's server setup. I've referenced it in 7 sessions since adding it. Every time, the AI uses it without being told. That's 7 explanations I didn't have to give.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;LearnBoard is part of &lt;a href="https://machina.chat" rel="noopener noreferrer"&gt;Machina&lt;/a&gt; — an open source suite of tools that close the gap between how you work and how your AI understands you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/machina-tools/machina.git
&lt;span class="nb"&gt;cd &lt;/span&gt;machina
bash setup.sh
&lt;span class="nb"&gt;cd &lt;/span&gt;tools/learnboard &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; node server.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open &lt;code&gt;http://localhost:4331&lt;/code&gt; in your browser.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://github.com/machina-tools/machina/tree/main/tools/learnboard" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://machina.chat" rel="noopener noreferrer"&gt;machina.chat&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devtools</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>How I Built BugCapture — From Screen Recording to AI-Ready Bug Report in One Click</title>
      <dc:creator>Machina Tools</dc:creator>
      <pubDate>Sun, 21 Jun 2026 02:11:59 +0000</pubDate>
      <link>https://dev.to/machina_tools/how-i-built-bugcapture-from-screen-recording-to-ai-ready-bug-report-in-one-click-4m2l</link>
      <guid>https://dev.to/machina_tools/how-i-built-bugcapture-from-screen-recording-to-ai-ready-bug-report-in-one-click-4m2l</guid>
      <description>&lt;p&gt;I was debugging a form alignment issue on a client's production server. Remote machine, no local environment. The kind of problem where you know exactly what you're seeing but translating it into words for your AI agent takes longer than finding the bug yourself.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"The second column in the input group is a few pixels wider than the first, but only when the browser is at an intermediate viewport width — somewhere between 768px and 900px — and only after a user has interacted with the first field. The offset appears to be about 12px..."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;By the time you've written that, you've already lost the time you were trying to save. And the AI's first three responses are clarifying questions, because even that description is ambiguous.&lt;/p&gt;

&lt;p&gt;This is the problem BugCapture solves: it turns a 47-second screen recording into a structured file your AI agent can act on immediately — with no text description from you, no manual screenshots, no copy-pasting error messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The insight: bugs have a natural format
&lt;/h2&gt;

&lt;p&gt;Modern AI agents — Claude, Copilot, GPT-4o — are multimodal. They can look at screenshots. They can read transcripts. The question isn't whether they can understand a bug from visual evidence; they clearly can. The question is: &lt;strong&gt;what format packages that evidence in a way that maximizes AI understanding?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer, after a lot of iteration, is a Markdown file with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A voice transcript from the developer reproducing the bug (what you're thinking while you click)&lt;/li&gt;
&lt;li&gt;Sequential screenshots at regular intervals (what the screen looked like over time)&lt;/li&gt;
&lt;li&gt;Optional SSH log capture (what the server was doing at the same time)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This combination gives the AI three independent channels of information about the same event. The transcript explains intent. The screenshots show the visual state. The logs show the runtime state. An AI reading all three can build a more accurate model of the bug than it could from any one source alone.&lt;/p&gt;

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

&lt;p&gt;The workflow is exactly three steps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Record&lt;/strong&gt; — click Record in the BugCapture browser interface. The page requests screen and microphone access. You reproduce the bug while narrating what you see. The recording is captured as a MediaRecorder stream — audio and video in parallel, fully local.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Process&lt;/strong&gt; — when you click Stop, the server runs two pipelines simultaneously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frame extraction&lt;/strong&gt;: ffmpeg extracts one screenshot every 3 seconds (configurable), converts them to JPEG at 85% quality. Up to 20 frames per recording.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transcription&lt;/strong&gt;: &lt;code&gt;@xenova/transformers&lt;/code&gt; runs the Whisper &lt;code&gt;base.en&lt;/code&gt; model on the audio — fully offline, no API key, no data upload. On a modern laptop, a 47-second recording transcribes in about 8 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Export&lt;/strong&gt; — you get a &lt;code&gt;.md&lt;/code&gt; file: screenshots embedded as base64 + the full transcript, structured for AI consumption.&lt;/p&gt;

&lt;p&gt;Drop that into Claude's context or Copilot Agent's workspace, and the AI has everything it needs. No text description from you. No manual screenshot upload.&lt;/p&gt;

&lt;h2&gt;
  
  
  LogLens: adding the server side
&lt;/h2&gt;

&lt;p&gt;BugCapture has an optional &lt;strong&gt;LogLens&lt;/strong&gt; mode: enable it before recording, and the server opens an SSH connection to your remote machine and tails the configured log files in parallel with the screen capture. When you export, the &lt;code&gt;.md&lt;/code&gt; includes a timestamped log capture alongside the visual evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real test
&lt;/h2&gt;

&lt;p&gt;The form alignment bug I mentioned: I recorded 47 seconds of screen and voice, exported the &lt;code&gt;.md&lt;/code&gt;, and dropped it into Copilot Agent.&lt;/p&gt;

&lt;p&gt;Copilot identified a conflicting &lt;code&gt;width&lt;/code&gt; rule in a child theme stylesheet that was being applied conditionally after the first user interaction triggered a re-render. The fix was three lines of CSS. Total time from "I see the bug" to "fix deployed": &lt;strong&gt;under two minutes&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key strengths
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Completely offline.&lt;/strong&gt; The Whisper model runs locally via ONNX. No transcription API, no upload, no account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI-agnostic output.&lt;/strong&gt; The &lt;code&gt;.md&lt;/code&gt; file works with any AI that accepts text: Claude, Copilot, GPT-4, Gemini, local models via Ollama.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero configuration for basic use.&lt;/strong&gt; Install Node.js, clone the repo, &lt;code&gt;node server.mjs&lt;/code&gt;. No API keys required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Screen + audio capture&lt;/td&gt;
&lt;td&gt;Web MediaRecorder API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frame extraction&lt;/td&gt;
&lt;td&gt;ffmpeg&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transcription&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@xenova/transformers&lt;/code&gt; + Whisper ONNX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSH log capture&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output format&lt;/td&gt;
&lt;td&gt;Markdown with base64 JPEG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;Node.js ESM, no framework&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;BugCapture is part of &lt;a href="https://machina.chat" rel="noopener noreferrer"&gt;Machina&lt;/a&gt; — an open source suite of tools that close the gap between "I see the bug" and "AI fixes the bug."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/machina-tools/machina.git
&lt;span class="nb"&gt;cd &lt;/span&gt;machina
bash setup.sh
&lt;span class="nb"&gt;cd &lt;/span&gt;tools/bugcapture &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; node server.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open &lt;code&gt;tools/bugcapture/index.html&lt;/code&gt; in your browser.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://github.com/machina-tools/machina/tree/main/tools/bugcapture" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://machina.chat" rel="noopener noreferrer"&gt;machina.chat&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devtools</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>I Built Open-Source AI Dev Tools to Close the Gap Between Seeing a Bug and Fixing It</title>
      <dc:creator>Machina Tools</dc:creator>
      <pubDate>Wed, 10 Jun 2026 12:45:26 +0000</pubDate>
      <link>https://dev.to/machina_tools/i-built-open-source-ai-dev-tools-to-close-the-gap-between-seeing-a-bug-and-fixing-it-3pc3</link>
      <guid>https://dev.to/machina_tools/i-built-open-source-ai-dev-tools-to-close-the-gap-between-seeing-a-bug-and-fixing-it-3pc3</guid>
      <description>&lt;p&gt;I've been using AI assistants like GitHub Copilot and Claude for debugging for over a year now. And I kept running into the same frustrating moment: I'd see a bug on my screen, but by the time I described it to the AI, I'd lose half the context — the exact error, what I clicked, what the logs said.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;&lt;a href="https://machina.chat" rel="noopener noreferrer"&gt;Machina&lt;/a&gt;&lt;/strong&gt; — a suite of open-source tools designed to close that gap.&lt;/p&gt;

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

&lt;p&gt;When you're debugging with an AI assistant, you need to transfer context: what you see, what happened, what the environment looks like. Doing this manually is slow and lossy. You forget things. You mis-describe things. The AI works with incomplete information.&lt;/p&gt;

&lt;p&gt;Machina automates that context transfer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  BugCapture
&lt;/h3&gt;

&lt;p&gt;Records your screen + audio, transcribes your voice with Whisper, takes a screenshot, and generates a ready-to-paste &lt;code&gt;.md&lt;/code&gt; file with everything an AI needs to understand your bug. No more copy-pasting error messages or describing what you see.&lt;/p&gt;

&lt;h3&gt;
  
  
  ContextForge
&lt;/h3&gt;

&lt;p&gt;Before starting an AI debugging session, run ContextForge. It pulls your recent git diff, SSH logs, and any BugCapture output into a single structured briefing. Your AI starts the session already knowing what changed and what broke.&lt;/p&gt;

&lt;h3&gt;
  
  
  LearnBoard
&lt;/h3&gt;

&lt;p&gt;A UI for &lt;code&gt;LEARNING.md&lt;/code&gt; — a persistent memory file for your AI. Instead of re-explaining your codebase conventions every session, LearnBoard lets you manage what the AI should always remember. With stats on how often each lesson is used.&lt;/p&gt;

&lt;h3&gt;
  
  
  PromptBoard
&lt;/h3&gt;

&lt;p&gt;A drag-and-drop canvas for building structured prompts. Combine context blocks, templates, and voice input (via browser or local Whisper) into prompts that consistently get better AI responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Source
&lt;/h2&gt;

&lt;p&gt;I wanted these tools to be something the community can extend and adapt. Every tool is self-contained: a &lt;code&gt;server.js&lt;/code&gt;, an &lt;code&gt;index.html&lt;/code&gt;, and a &lt;code&gt;package.json&lt;/code&gt;. Run &lt;code&gt;bash setup.sh&lt;/code&gt; and you're done.&lt;/p&gt;

&lt;p&gt;The repo is at &lt;a href="https://github.com/machina-tools/machina" rel="noopener noreferrer"&gt;github.com/machina-tools/machina&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Launch Day
&lt;/h2&gt;

&lt;p&gt;Today is launch day on Product Hunt! If these tools sound useful to you, I'd love your support:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://www.producthunt.com/posts/machina" rel="noopener noreferrer"&gt;Vote on Product Hunt&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer any questions in the comments — about the tools, the architecture, or the debugging workflow that inspired this.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Node.js, Whisper (OpenAI), and a lot of late-night debugging sessions.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
