<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>R on Posit Open Source</title>
    <link>https://opensource.posit.co/languages/r/</link>
    <description>Recent content in R on Posit Open Source</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Thu, 16 Jul 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://opensource.posit.co/languages/r/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Introducing lorax: Speaking for the Tree-Based Models</title>
      <link>https://opensource.posit.co/blog/2026-07-16_lorax/</link>
      <pubDate>Thu, 16 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-16_lorax/</guid>
      <dc:creator>Max Kuhn</dc:creator><description><![CDATA[<p>When building tree-based models, such as <a href="https://cran.r-project.org/package=rpart" target="_blank" rel="noopener">CART</a>, <a href="https://en.wikipedia.org/wiki/Random_forest" target="_blank" rel="noopener">random forests</a>, or <a href="https://en.wikipedia.org/wiki/XGBoost" target="_blank" rel="noopener">XGBoost</a>, we might be interested in knowing a little more about how the model works. For example, if a CART and XGBoost model had roughly the same performance, we might want to characterize how complex each is so that we are more informed about which to prefer. Knowing how many predictors were used, how many terminal nodes are in the tree, and similar characteristics can help understand the model. We might desire to visualize the tree (or a tree in the ensemble), and so on.</p>
<p>The problem is that many packages store the tree&rsquo;s splits in different ways or offer incompatible APIs to access different characteristics. lorax helps capture this information for many different implementations.</p>
<p>You can install the CRAN version of lorax via</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;lorax&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>or get the development version using</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">pak</span><span class="o">::</span><span class="nf">pak</span><span class="p">(</span><span class="s">&#34;tidymodels/lorax&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Let&rsquo;s start by using the ranger package to create a random forest model using the food delivery data in the modeldata package. For illustration, the trees will be coerced to be more shallow than usual so that we can better plot them using the <code>min.node.size</code> argument:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">tidymodels</span><span class="p">)</span> <span class="c1"># &lt;- to easily get dplyr, tidyr, ggplot2, etc</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ranger</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">lorax</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">set.seed</span><span class="p">(</span><span class="m">872</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">rgr_fit</span> <span class="o">&lt;-</span>
</span></span><span class="line"><span class="cl">  <span class="nf">ranger</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">time_to_delivery</span> <span class="o">~</span> <span class="n">.,</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">modeldata</span><span class="o">::</span><span class="n">deliveries</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">num.trees</span> <span class="o">=</span> <span class="m">1000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">min.node.size</span> <span class="o">=</span> <span class="m">5000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">importance</span> <span class="o">=</span> <span class="s">&#34;impurity&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">rgr_fit</span></span></span></code></pre></div></div>
<pre><code>Ranger result

Call:
 ranger(time_to_delivery ~ ., data = modeldata::deliveries, num.trees = 1000,      min.node.size = 5000, importance = &quot;impurity&quot;) 

Type:                             Regression 
Number of trees:                  1000 
Sample size:                      10012 
Number of independent variables:  30 
Mtry:                             5 
Target node size:                 5000 
Variable importance mode:         impurity 
Splitrule:                        variance 
OOB prediction error (MSE):       24.26389 
R squared (OOB):                  0.4879119 
</code></pre>
<h2 id="visualizing-the-trees">Visualizing the Trees
</h2>
<p>lorax contains methods for the <code>as.party()</code> function in the partykit package. This enables us to use all of the methods from that package. For example, <code>plot.party()</code> is an excellent visualization tool for the tree. Random forest has <em>many</em> trees and we can plot any of them:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">rgr_fit</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">as.party</span><span class="p">(</span><span class="n">tree</span> <span class="o">=</span> <span class="m">1</span><span class="p">,</span> <span class="n">data</span> <span class="o">=</span> <span class="n">modeldata</span><span class="o">::</span><span class="n">deliveries</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">plot</span><span class="p">()</span></span></span></code></pre></div></div>
<img src="https://opensource.posit.co/blog/2026-07-16_lorax/index.markdown_strict_files/figure-markdown_strict/rgr-plots-1.png" style="width:100.0%" data-fig-alt="Diagram of tree 1 from the random forest. Internal nodes show the splitting predictors with branches labeled by split values, ending in boxplots of the delivery time distribution in each terminal node." />
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">rgr_fit</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">as.party</span><span class="p">(</span><span class="n">tree</span> <span class="o">=</span> <span class="m">100</span><span class="p">,</span> <span class="n">data</span> <span class="o">=</span> <span class="n">modeldata</span><span class="o">::</span><span class="n">deliveries</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">plot</span><span class="p">()</span></span></span></code></pre></div></div>
<img src="https://opensource.posit.co/blog/2026-07-16_lorax/index.markdown_strict_files/figure-markdown_strict/rgr-plots-2.png" style="width:100.0%" data-fig-alt="Diagram of tree 100 from the random forest, in the same format but with different splits and predictors." />
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">rgr_fit</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">as.party</span><span class="p">(</span><span class="n">tree</span> <span class="o">=</span> <span class="m">1000</span><span class="p">,</span> <span class="n">data</span> <span class="o">=</span> <span class="n">modeldata</span><span class="o">::</span><span class="n">deliveries</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">plot</span><span class="p">()</span></span></span></code></pre></div></div>
<img src="https://opensource.posit.co/blog/2026-07-16_lorax/index.markdown_strict_files/figure-markdown_strict/rgr-plots-3.png" style="width:100.0%" data-fig-alt="Diagram of tree 1000 from the random forest, in the same format but with different splits and predictors." />
<h2 id="which-predictors-were-used">Which Predictors Were Used?
</h2>
<p>Since trees automatically conduct <em>feature selection</em> as the model is trained, it helps to know which ones are <em>actually</em> used by the model. The <code>active_predictors()</code> function does just that:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">rgr_vars</span> <span class="o">&lt;-</span> <span class="nf">active_predictors</span><span class="p">(</span><span class="n">rgr_fit</span><span class="p">,</span> <span class="n">tree</span> <span class="o">=</span> <span class="m">1</span><span class="o">:</span><span class="m">1000</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">rgr_vars</span></span></span></code></pre></div></div>
<pre><code># A tibble: 1,000 × 2
   active_predictors  tree
   &lt;list&gt;            &lt;int&gt;
 1 &lt;chr [4]&gt;             1
 2 &lt;chr [5]&gt;             2
 3 &lt;chr [3]&gt;             3
 4 &lt;chr [4]&gt;             4
 5 &lt;chr [4]&gt;             5
 6 &lt;chr [3]&gt;             6
 7 &lt;chr [3]&gt;             7
 8 &lt;chr [7]&gt;             8
 9 &lt;chr [3]&gt;             9
10 &lt;chr [6]&gt;            10
# ℹ 990 more rows
</code></pre>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># The column names are in a nested vector:</span>
</span></span><span class="line"><span class="cl"><span class="n">rgr_vars</span><span class="o">$</span><span class="n">active_predictors[[1]]</span></span></span></code></pre></div></div>
<pre><code>[1] &quot;day&quot;     &quot;hour&quot;    &quot;item_10&quot; &quot;item_23&quot;
</code></pre>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># We can expand the list too:</span>
</span></span><span class="line"><span class="cl"><span class="n">rgr_vars</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">unnest</span><span class="p">(</span><span class="n">cols</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="n">active_predictors</span><span class="p">))</span></span></span></code></pre></div></div>
<pre><code># A tibble: 4,306 × 2
   active_predictors  tree
   &lt;chr&gt;             &lt;int&gt;
 1 day                   1
 2 hour                  1
 3 item_10               1
 4 item_23               1
 5 day                   2
 6 distance              2
 7 item_10               2
 8 item_12               2
 9 item_26               2
10 hour                  3
# ℹ 4,296 more rows
</code></pre>
<p>How often are predictors used?</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">rgr_vars</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">unnest</span><span class="p">(</span><span class="n">cols</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="n">active_predictors</span><span class="p">))</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">count</span><span class="p">(</span><span class="n">active_predictors</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">arrange</span><span class="p">(</span><span class="n">active_predictors</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code># A tibble: 30 × 2
   active_predictors     n
   &lt;chr&gt;             &lt;int&gt;
 1 day                 516
 2 distance            507
 3 hour                623
 4 item_01             329
 5 item_02             131
 6 item_03              57
 7 item_04              66
 8 item_05              26
 9 item_06              92
10 item_07              91
# ℹ 20 more rows
</code></pre>
<p>How many predictors are used in each rule (on average)? A rule is the full logical statement that defines the path to the terminal nodes.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">rgr_vars</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">mutate</span><span class="p">(</span><span class="n">num_vars</span> <span class="o">=</span> <span class="nf">map_int</span><span class="p">(</span><span class="n">active_predictors</span><span class="p">,</span> <span class="o">~</span> <span class="nf">length</span><span class="p">(</span><span class="n">.x</span><span class="p">)))</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">summarize</span><span class="p">(</span><span class="n">mean_num_vars</span> <span class="o">=</span> <span class="nf">mean</span><span class="p">(</span><span class="n">num_vars</span><span class="p">))</span></span></span></code></pre></div></div>
<pre><code># A tibble: 1 × 1
  mean_num_vars
          &lt;dbl&gt;
1          4.31
</code></pre>
<p>Many packages can compute <em>variable importance scores</em> for each predictor but each has a different interface. The lorax package has an accessor function to pull these from the model called <code>var_imp()</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">var_imp</span><span class="p">(</span><span class="n">rgr_fit</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code># A tibble: 30 × 2
   term     estimate
   &lt;chr&gt;       &lt;dbl&gt;
 1 hour     108892. 
 2 day       16054. 
 3 distance  25689. 
 4 item_01     673. 
 5 item_02      86.3
 6 item_03      30.4
 7 item_04      43.7
 8 item_05      11.0
 9 item_06      50.1
10 item_07      55.2
# ℹ 20 more rows
</code></pre>
<h2 id="examining-rules">Examining Rules
</h2>
<p>We can also get detailed information on the model&rsquo;s rules (for each tree). Using the same ranger model fit:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">rgr_rules</span> <span class="o">&lt;-</span> <span class="nf">extract_rules</span><span class="p">(</span><span class="n">rgr_fit</span><span class="p">,</span> <span class="n">data</span> <span class="o">=</span> <span class="n">modeldata</span><span class="o">::</span><span class="n">deliveries</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">rgr_rules</span></span></span></code></pre></div></div>
<pre><code># A tibble: 5 × 3
     id rules       tree
  &lt;int&gt; &lt;list&gt;     &lt;int&gt;
1     3 &lt;language&gt;     1
2     5 &lt;language&gt;     1
3     7 &lt;language&gt;     1
4     8 &lt;language&gt;     1
5     9 &lt;language&gt;     1
</code></pre>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># With components</span>
</span></span><span class="line"><span class="cl"><span class="n">rgr_rules</span><span class="o">$</span><span class="n">rules[[1]]</span> <span class="o">|&gt;</span> <span class="nf">class</span><span class="p">()</span></span></span></code></pre></div></div>
<pre><code>[1] &quot;call&quot;
</code></pre>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">rgr_rules</span><span class="o">$</span><span class="n">rules[[1]]</span></span></span></code></pre></div></div>
<pre><code>item_23 &lt;= 0.5 &amp; hour &lt;= 14.6645
</code></pre>
<p>We can compute on these and/or use them to determine which specific data were contained in the terminal node:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">modeldata</span><span class="o">::</span><span class="n">deliveries</span> <span class="o">|&gt;</span> <span class="nf">filter</span><span class="p">(</span><span class="o">!!</span><span class="n">rgr_rules</span><span class="o">$</span><span class="n">rules[[1]]</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code># A tibble: 2,415 × 31
   time_to_delivery  hour day   distance item_01 item_02 item_03 item_04 item_05
              &lt;dbl&gt; &lt;dbl&gt; &lt;fct&gt;    &lt;dbl&gt;   &lt;int&gt;   &lt;int&gt;   &lt;int&gt;   &lt;int&gt;   &lt;int&gt;
 1             16.1  11.9 Thu       3.15       0       0       2       0       0
 2             19.6  13.0 Sat       3.35       1       0       0       1       0
 3             17.4  11.9 Sun       2.75       0       2       1       0       0
 4             18.0  12.1 Tue       2.4        0       0       0       1       0
 5             22.1  14.4 Thu       2.69       0       0       0       0       0
 6             17.6  12.9 Sat       2.47       0       1       0       0       0
 7             17.0  12.3 Sat       3.88       0       0       0       0       0
 8             19.5  13.5 Tue       3.55       0       0       0       0       0
 9             17.6  12.9 Fri       2.88       0       0       1       1       0
10             21.6  14.3 Sat       3          0       0       0       1       0
# ℹ 2,405 more rows
# ℹ 22 more variables: item_06 &lt;int&gt;, item_07 &lt;int&gt;, item_08 &lt;int&gt;,
#   item_09 &lt;int&gt;, item_10 &lt;int&gt;, item_11 &lt;int&gt;, item_12 &lt;int&gt;, item_13 &lt;int&gt;,
#   item_14 &lt;int&gt;, item_15 &lt;int&gt;, item_16 &lt;int&gt;, item_17 &lt;int&gt;, item_18 &lt;int&gt;,
#   item_19 &lt;int&gt;, item_20 &lt;int&gt;, item_21 &lt;int&gt;, item_22 &lt;int&gt;, item_23 &lt;int&gt;,
#   item_24 &lt;int&gt;, item_25 &lt;int&gt;, item_26 &lt;int&gt;, item_27 &lt;int&gt;
</code></pre>
<h2 id="a-list-of-methods">A List of Methods
</h2>
<p>Here are the details of which models and methods are supported in the first CRAN version:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">class</th>
          <th style="text-align: left">var_imp</th>
          <th style="text-align: left">active_predictors</th>
          <th style="text-align: left">as.party</th>
          <th style="text-align: left">extract_rules</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">bart</td>
          <td style="text-align: left">n/a</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
      </tr>
      <tr>
          <td style="text-align: left">C5.0</td>
          <td style="text-align: left">n/a</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
      </tr>
      <tr>
          <td style="text-align: left">cforest</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">n/a</td>
          <td style="text-align: left">✔</td>
      </tr>
      <tr>
          <td style="text-align: left">cubist</td>
          <td style="text-align: left">✖</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✖</td>
          <td style="text-align: left">✔</td>
      </tr>
      <tr>
          <td style="text-align: left">grf</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
      </tr>
      <tr>
          <td style="text-align: left">lgb.Booster</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
      </tr>
      <tr>
          <td style="text-align: left">ObliqueForest</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✖</td>
          <td style="text-align: left">✔</td>
      </tr>
      <tr>
          <td style="text-align: left">party</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">n/a</td>
          <td style="text-align: left">✔</td>
      </tr>
      <tr>
          <td style="text-align: left">randomForest</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
      </tr>
      <tr>
          <td style="text-align: left">ranger</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
      </tr>
      <tr>
          <td style="text-align: left">rpart</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">n/a</td>
          <td style="text-align: left">✔</td>
      </tr>
      <tr>
          <td style="text-align: left">xgb.Booster</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
          <td style="text-align: left">✔</td>
      </tr>
  </tbody>
</table>
<p>Note that <code>as.party.rpart()</code> is in the partykit package and that cforest is made out of party objects.</p>
<h2 id="whats-next">What&rsquo;s Next?
</h2>
<p>We&rsquo;ll work on adding <a href="https://catboost.ai/" target="_blank" rel="noopener">CatBoost</a> models to the list of supported methods. Please <a href="https://github.com/tidymodels/lorax/issues" target="_blank" rel="noopener">add an issue</a> if there are other aspects of trees that should be quantified in these models.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-16_lorax/featured.png" length="511489" type="image/png" />
    </item>
    <item>
      <title>Tips for managing your Python &amp; R environments in Positron</title>
      <link>https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/</link>
      <pubDate>Wed, 15 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/</guid>
      <dc:creator>Cindy Tong</dc:creator>
      <dc:creator>Brice Stacey</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a> is the Posit next-generation IDE for data science. Positron is an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>If you work across Python and R or juggle multiple projects with different dependency needs, environment management is probably one of the most challenging parts of your workflow. <a href="https://positron.posit.co/" target="_blank" rel="noopener">Positron</a> comes with several out-of-the-box ways to help simplify environment management. Here are a few tips to streamline your workflow.</p>
<h2 id="1-understand-how-positron-discovers-your-environment">1: Understand how Positron discovers your environment
</h2>
<p>Positron does not just look at your system PATH. For Python, it actively discovers venv, uv, pyenv, and conda environments. If Positron is not seeing your project&rsquo;s virtual environment, you can add custom search locations via the <a href="positron://settings/python.interpreters.include"><code>python.interpreters.include</code></a> setting, or trigger a manual rescan with <em>Interpreter: Discover All Interpreters</em>. Learn more about <a href="https://positron.posit.co/python-installations.html#python-installation-discovery" target="_blank" rel="noopener">Python discovery in the Positron documentation</a>.</p>
<p>For R, discovery works differently. Positron consults various sources to build the list of R interpreters. These include your PATH, R root folders based on specific operating systems, well-known executable locations, and on Windows the registry. You can customize your R discovery through a few settings including <a href="positron://settings/positron.r.customRootFolders"><code>positron.r.customRootFolders</code></a> and <a href="positron://settings/positron.r.customBinaries"><code>positron.r.customBinaries</code></a>. Learn more about <a href="https://positron.posit.co/r-installations.html#customizing-r-discovery" target="_blank" rel="noopener">R discovery in the Positron documentation</a>.</p>
<h2 id="2-use-the-interpreter-selector">2. Use the Interpreter Selector
</h2>
<p>To begin your first session, click &ldquo;Start Session&rdquo; in the top right corner and select your preferred R or Python interpreter. Positron can run multiple R and Python interpreter sessions at once, but only one is ever the active session at a given moment. The Interpreter Selector always shows you the active session and its status (idle, busy, or shut down) and you can use it to switch between or start additional sessions.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/active-interpreter-session.png" width="100%" data-fig-align="center" data-fig-alt="Use the Interpreter Selector to change your active session" />
<h2 id="3-install-python-with-uv">3. Install Python with uv
</h2>
<p>If Positron does not find a usable Python on your machine, it will offer to install Python via uv to help streamline your setup. If you prefer to manage the installation yourself, you can disable uv with the <a href="positron://settings/python.allowUvPythonInstall"><code>python.allowUvPythonInstall</code></a> setting. Check out our <a href="https://opensource.posit.co/blog/2026-07-08_positron-uv/" target="_blank" rel="noopener">blog post exploring on-demand Python installation in Positron</a> or explore configurations in our <a href="https://positron.posit.co/python-installations.html#troubleshooting" target="_blank" rel="noopener">Python installation documentation</a>.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/install-python-with-uv.png" width="100%" data-fig-align="center" data-fig-alt="Install Python via uv" />
<h2 id="4-manage-packages-with-the-packages-pane">4. Manage packages with the Packages Pane
</h2>
<p>The Packages Pane in Positron lets you manage the packages installed in your active session. Whether you use pip, uv, conda, pak, base R, or renv, you can browse installed packages and search package repositories. You can also track outdated packages and install, update, or uninstall packages without leaving Positron or writing any code.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/packages-pane.gif" width="100%"  data-fig-align="center" data-fig-alt="Manage Python and R packages in the Packages Pane" />
<h2 id="5-view-package-documentation">5. View package documentation
</h2>
<p>If you need to learn more about a specific package, the Packages Pane has buttons to navigate to the source documentation or link to the package&rsquo;s website. You can also pull up the <a href="https://positron.posit.co/help-pane.html" target="_blank" rel="noopener">Help Pane</a> for any reference in the Console using the <code>?</code> operator, including both packages and functions.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/packages-pane-help.png" width="100%" data-fig-align="center" data-fig-alt="View source documentation for specific packages in the Help Pane" />
<h2 id="6-start-projects-with-new-folder-from-template">6. Start projects with New Folder from Template
</h2>
<p>The New Folder from Template flow helps you start new projects faster. Instead of running multiple setup commands you can make a few selections and Positron helps you set up an environment directory, version control, directory structure, and an interpreter instance. Learn more about the <a href="https://positron.posit.co/folder-templates.html" target="_blank" rel="noopener">Python and R templates</a> available in the documentation.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/new-folder-from-template.png" width="100%" data-fig-align="center" data-fig-alt="New Folder from Template helps you start new projects faster with ready to use environments" />
<h2 id="7-make-use-of-the-command-palette">7. Make use of the Command Palette
</h2>
<p>We recommend taking advantage of the following commands when managing your environments:</p>
<ul>
<li><em>Interpreter: Discover All Interpreters</em>: Find new interpreters</li>
<li><em>Interpreter: Select Session</em>: Select a running interpreter session</li>
<li><em>Python: Create Environment</em>: Create a new virtual environment</li>
</ul>
<h2 id="8-let-posit-assistant-help-manage-your-environment">8. Let Posit Assistant help manage your environment
</h2>
<p><a href="https://assistant.posit.co/docs/features/context-management/" target="_blank" rel="noopener">Posit Assistant</a> has knowledge of your R and Python session including the language, version, names, and types of variables in your environment. You can prompt Posit Assistant to set up environments and troubleshoot issues you run into.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/assistant-installed-packages.png" width="100%" data-fig-align="center" data-fig-alt="Posit Assistant in Positron showing a summary of packages installed in the user's Python environment, organized by category." />
<p>Have an idea for how we can improve environment management in Positron? We would love to hear from you in a <a href="https://github.com/posit-dev/positron/discussions" target="_blank" rel="noopener">discussion on GitHub</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/featured.svg" length="75593" type="image/svg&#43;xml" />
    </item>
    <item>
      <title>httr2 1.3.0</title>
      <link>https://opensource.posit.co/blog/2026-07-14_httr2-1-3-0/</link>
      <pubDate>Tue, 14 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-14_httr2-1-3-0/</guid>
      <dc:creator>Hadley Wickham</dc:creator><description><![CDATA[<p>We&rsquo;re chuffed to announce the release of <a href="https://httr2.r-lib.org" target="_blank" rel="noopener">httr2</a> 1.3.0. httr2 makes it easy to work with web APIs from R, providing a pipeable interface for building requests and a suite of tools for processing the responses.</p>
<p>Get it now:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># install.packages(&#34;pak&#34;)</span>
</span></span><span class="line"><span class="cl"><span class="n">pak</span><span class="o">::</span><span class="nf">pak</span><span class="p">(</span><span class="s">&#34;httr2&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>The headline changes in this release all relate to OAuth token caching. They happen behind the scenes, so you shouldn&rsquo;t need to change any code, but you&rsquo;ll likely need to re-authenticate once after upgrading. The rest of this post explains why, and rounds up a handful of smaller features that landed in recent patch releases. See a full list of changes in the <a href="https://github.com/r-lib/httr2/releases/tag/v1.3.0" target="_blank" rel="noopener">release notes</a>.</p>
<h2 id="oauth-token-caching">OAuth token caching
</h2>
<p>First, a bit of background. When you authenticate with an API using OAuth, the API typically returns a refresh token that you can reuse for a set period. That means you don&rsquo;t have to log in every single time you use the API, which makes for a much nicer experience. To reuse a token across R sessions, however, httr2 has to write it to disk, and that creates a risk: anyone who can read the token could use it to access the API as you.</p>
<p>httr2 takes every precaution it can to keep these tokens safe. They&rsquo;re stored in a user-local cache directory, which is usually excluded from backups and, because it lives outside your working directory, carries no risk of being accidentally committed to git. On top of that, the tokens are encrypted so that only httr2 can read them.</p>
<p>Each token&rsquo;s path on disk is derived from a hash computed with <code>rlang::hash()</code>. We recently discovered a bug in that hashing code, and fixing it changes every hash. As a result, httr2 can no longer find your existing cached tokens, so you&rsquo;ll need to re-authenticate once after upgrading.</p>
<p>Since that change already invalidates your cached tokens, we took the opportunity to move where they&rsquo;re stored. Previously we used the rappdirs package to locate the cache directory, but since R 4.0.0 there&rsquo;s been a better built-in alternative: <code>tools::R_user_dir(&quot;httr2&quot;, which = &quot;cache&quot;)</code>. We&rsquo;ve switched to that, which makes httr2&rsquo;s cache location consistent with other packages and removes the rappdirs dependency.</p>
<p>Making these changes surfaced a long-standing bug. Every time httr2 loads, it&rsquo;s supposed to automatically delete any cached tokens older than 30 days, so that stale tokens don&rsquo;t linger indefinitely. Unfortunately, that pruning never actually worked. Now it does, so old tokens will be cleaned up once they pass 30 days, regardless of which cache directory or hashing algorithm produced them. This is good security practice: the longer a credential sits on disk, the more opportunities there are for it to leak, so removing tokens you&rsquo;re no longer using shrinks that window of exposure. If you&rsquo;d like to prune every cached token yourself, you can call <code>oauth_cache_prune(max_age_days = 0)</code>.</p>
<h2 id="other-features">Other features
</h2>
<p>Alongside the token caching work, this release also rolls up a few smaller features that first appeared in recent patch releases:</p>
<ul>
<li>
<p><strong>Faster streaming</strong> (1.2.3): <code>resp_stream_lines()</code>, <code>resp_stream_sse()</code>, and <code>resp_stream_aws()</code> now decode whole chunks at a time and hold the results in a queue, instead of rescanning and recopying the buffer for every line or event. This makes memory use and run time scale linearly rather than quadratically, so large streams are dramatically faster (e.g. reading a 1 MB response of short lines is now around 200x faster and uses around 180x less memory). <code>resp_stream_sse()</code> is used heavily by <a href="https://ellmer.tidyverse.org" target="_blank" rel="noopener">ellmer</a>, so this should make your LLM streaming feel a little zippier.</p>
</li>
<li>
<p><strong>OAuth server metadata discovery</strong> (1.2.3): the new <code>oauth_server_metadata()</code> discovers an OAuth/OIDC issuer&rsquo;s endpoints from its <code>.well-known</code> document, and <code>oauth_client()</code> gains a <code>metadata</code> argument so you can supply all of those endpoints at once.</p>
</li>
<li>
<p><strong>OpenTelemetry tracing</strong> (1.2.2): httr2 now emits OpenTelemetry traces for all requests when tracing is enabled. This requires the otelsdk package and is part of our cross-package work to improve the <a href="https://opensource.posit.co/blog/2026-05-07_opentelemetry/" target="_blank" rel="noopener">observability of R packages</a>.</p>
</li>
<li>
<p><strong><code>httr2_translate()</code></strong> (1.2.3): this new function translates an httr2 request into the equivalent curl command. It&rsquo;s handy for debugging, or for creating reprexes to share with people who don&rsquo;t use R.</p>
</li>
</ul>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-14_httr2-1-3-0/featured.jpg" length="312741" type="image/jpeg" />
    </item>
    <item>
      <title>useR! 2026: CRDTs, community, and contributing back to R</title>
      <link>https://opensource.posit.co/blog/2026-07-14_user-2026-warsaw/</link>
      <pubDate>Tue, 14 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-14_user-2026-warsaw/</guid>
      <dc:creator>Charlie Gao</dc:creator><description><![CDATA[<p>Although the title of this post starts with &ldquo;CRDTs&rdquo; (for maximal alliterative effect), it is in fact the other two (community and contributing back) which are way more important to me. Sometimes it takes a conference, a substantial chunk of time away from the desk, to step back and re-calibrate our perspectives: to really appreciate the sheer breadth of <em>good work</em> taking place within the community.</p>
<p>And this community is ours. From joining my very first useR! online in 2022, through to Salzburg in 2024 - SatRdays London in between, the R Project Sprint in Warwick (UK) in 2023, the R Dev Days since, and of course posit::conf(2024) Seattle, posit::conf(2025) Atlanta, and Japan.R 2025&hellip; I may not have been part of this community for the most number of years, but I&rsquo;ve certainly appreciated its warmth and openness.</p>
<p>And so coming back to a European useR! was exciting.</p>
<h2 id="distributing-state-crdts-for-real-time-collaboration">Distributing state: CRDTs for real-time collaboration
</h2>
<p>The R community knows how to distribute compute - I&rsquo;ve talked extensively on this subject, mainly surrounding my creation {<a href="https://mirai.r-lib.org/" target="_blank" rel="noopener">mirai</a>}, an async framework that brings high-performance parallel and distributed computing to R. This lets you send tasks just as easily to a Slurm cluster as to processes on your own machine.</p>
<p>My talk this time was going to be about something different, but complementary: distributing state. A CRDT, or Conflict-free Replicated Data Type, is a data structure that facilitates collaboration by having a property called strong eventual consistency. Without going into the details, this allows it to always merge conflict-free, and is the technology behind many collaborative text editors. Automerge, a particular CRDT implementation, is what we&rsquo;re using in Quarto 2 to make it collaborative out-of-the-box.</p>
<p>In my talk, I showed a Shiny app in an R session editing a document on one of our Quarto collaborative sync servers. It used the {<a href="https://posit-dev.github.io/automerge-r/" target="_blank" rel="noopener">automerge</a>} and {<a href="https://posit-dev.github.io/autosync/" target="_blank" rel="noopener">autosync</a>} packages, which we created to let R manipulate these structures and sync them over the network - on a par with reference implementations in JavaScript and Rust. Automerge was created by Ink and Switch, an independent research lab for local-first software, and we&rsquo;re glad to provide a link between the two communities.</p>
<h2 id="distributing-compute-mirai-and-mori-in-the-wild">Distributing compute: mirai and mori in the wild
</h2>
<p>Apart from talking a lot about CRDTs and the three killer features of Quarto 2 (orders of magnitude faster, collaborative editing built-in with live preview, editable in the preview and source views), I also got pulled aside by people in the hallways to talk about {<a href="https://mirai.r-lib.org/" target="_blank" rel="noopener">mirai</a>} and {<a href="https://shikokuchuo.net/mori/" target="_blank" rel="noopener">mori</a>}.</p>
<p>Some of these were people working in the life sciences industry, using these packages for serious scientific research and innovation. This has always been a sector that I&rsquo;ve had immense respect for - from my earliest collaboration with Will Landau (Eli Lilly &amp; Co.). Open source software is often regarded as &lsquo;high-leverage&rsquo; in terms of how often and widely it&rsquo;s used, and when deployed in such high-impact settings, the end goals can be especially motivating and rewarding.</p>
<p>It&rsquo;s been extremely gratifying to see that {<a href="https://mirai.r-lib.org/" target="_blank" rel="noopener">mirai</a>} has percolated throughout the community consciousness. Considering that I was only introducing the package at useR! 2024 in Salzburg, it was nice to be able to introduce <em>myself</em> to people this time simply as &ldquo;the author of mirai&rdquo;. By 2027, I&rsquo;m hoping this will also be possible with mori!</p>
<h2 id="the-best-part-the-people">The best part: the people
</h2>
<p>Given that only a few of my colleagues could make it to useR! this year, I was slightly apprehensive going in. This fear was dispelled at the very first reception event, where I met so many people from past R Dev Days, amongst them Tina Roszos, who simply beamed at me from across the room! R Core also turned out in force and it was nice to meet Peter Dalgaard and Robert Gentleman for the first time, as well as saying hello again to Luke Tierney, Uwe Ligges and everyone who came.</p>
<p>It&rsquo;s always great to catch up with familiar faces - Gergely Daróczi (maintainer of R&rsquo;s Weblate platform), members of the mlr group (Martin Binder, Marc Becker, Maximilian Mücke), and many others. However, some of my best conversations this year were with people I had met for the first time. I was fortunate that for some, this was their first in-person useR!, having been part of the R community for many years.</p>
<p>It was especially nice to be welcomed by entire groups of people, for example the <a href="https://numbat.space" target="_blank" rel="noopener">NUMBATs</a> headed by Prof. Dianne Cook, who was also the opening keynote speaker of the conference. I got to be an honorary NUMBAT for one photo:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-07-14_user-2026-warsaw/images/numbat.jpg"
      alt="The Monash &ldquo;NUMBAT&rdquo; group posing inside a &ldquo;useR! 2026 Warsaw&rdquo; photo-frame prop covered in R-package hex stickers." 
      loading="lazy"
    >
  </figure></div>
</p>
<h2 id="contributing-back-r-dev-day">Contributing back: R Dev Day
</h2>
<p>R Dev Day followed the conference on the Friday. Thanks again to Heather Turner and Ella Kaye for tirelessly organising these. They are simply great opportunities to contribute back to R&rsquo;s source code itself. I do believe that these events have played an important role in fostering and sustaining our community.</p>
<p>I worked closely with Tymek (Tymoteusz Makowski) on two C-level I/O bugs. Tymek was the only person brave enough to tackle these. This proved to be a fruitful collaboration as we&rsquo;d posted a patch by the end of the day (<a href="https://bugs.r-project.org/show_bug.cgi?id=19101" target="_blank" rel="noopener">PR #19101</a>). This patch has since been accepted into the R source, without modification! It&rsquo;s proved the most productive of any R Dev Day I&rsquo;ve attended thus far.</p>
<p>Special thanks to Mitchell O&rsquo;Hara-Wild for supplying the Dev Day group photograph, which I&rsquo;m using as the banner image for this blog post.</p>
<h2 id="looking-ahead">Looking ahead
</h2>
<p>There are many people I&rsquo;ve not mentioned by name - that&rsquo;s just to prevent this entire post from being a name-drop. I&rsquo;ve enjoyed speaking to every one of you. There is so much talent out there, and I look forward to catching up again and marvelling at all you&rsquo;ve been working on!</p>
<p>See you next at <a href="https://conf.posit.co/2026/" target="_blank" rel="noopener">posit::conf(2026)</a> Houston!</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-14_user-2026-warsaw/images/hero.jpg" length="575131" type="image/jpeg" />
    </item>
    <item>
      <title>Positron July Release Highlights</title>
      <link>https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/</link>
      <pubDate>Mon, 13 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/</guid>
      <dc:creator>Julia Silge</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a> is Posit&rsquo;s new, next-generation IDE for data science. Positron is designed to be an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>Welcome back to another edition of our monthly Positron updates! Each month we share highlights from our <a href="https://positron.posit.co/release-notes" target="_blank" rel="noopener">latest release</a> and useful resources. <a href="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/" target="_blank" rel="noopener">Last release</a> we told you that several major features were on track to leave preview in July, and the time is now here! The new Notebook editor, the Packages pane, and Posit Assistant are all now generally available.</p>
<h2 id="positron-notebook-editor">Positron Notebook Editor
</h2>
<p>Positron&rsquo;s <a href="https://positron.posit.co/positron-notebook-editor" target="_blank" rel="noopener">new Notebook editor</a> is now the default experience for Jupyter (<code>.ipynb</code>) files. This release brings a long list of additions, including split-pane editing, cell tag management, and executing a line or selection within a cell with <kbd>Cmd/Ctrl+Shift+Enter</kbd>. You can export notebooks to Quarto, Python, or R, and inline PDF rendering makes it easier to work with generated output. If you are coming from JupyterLab, you will find familiar keyboard shortcuts, and we have improved output fidelity for Mermaid diagrams, htmlwidgets, and ipywidgets.</p>
<img src="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/positron-notebook-editor.png" data-fig-align="center" data-fig-alt="The Notebook editor in Positron, showing a Jupyter notebook with a Mermaid diagram." />
<p>Posit has built on and for the Jupyter ecosystem for years now, and we are excited to have recently <a href="https://opensource.posit.co/blog/2026-06-25_posit-joins-jupyter-foundation/" target="_blank" rel="noopener">joined the Jupyter Foundation</a>.</p>
<h2 id="packages-pane">Packages pane
</h2>
<p>The <a href="https://positron.posit.co/packages-pane" target="_blank" rel="noopener">Packages pane</a> has also come out of preview. It gives you a live view of the R and Python packages installed in your active session, so you can search, install, update, and remove packages, and jump to their documentation, without leaving Positron.</p>
<img src="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/packages-pane.gif" data-fig-align="center" data-fig-alt="The Packages pane in Positron, showing installed Python packages with a detail editor." />
<p>This release also makes the pane more capable. Clicking a package opens a detail editor with its overview, metadata, and actions, similar to the Extensions pane. Installing a package searches results as you type, and each row has a button that opens the package&rsquo;s website. Update indicators appear immediately on a new session from a cached snapshot, and <strong>Update All Packages</strong> reports exactly what changed. Package operations keep your environment consistent too, resolving against a workspace <code>requirements.txt</code> for Python and updating the <code>renv.lock</code> snapshot for R.</p>
<h2 id="posit-assistant">Posit Assistant
</h2>
<p><a href="https://pos.it/assistant" target="_blank" rel="noopener">Posit Assistant</a>, our unified, data-science-focused approach to AI assistance, is now out of preview and generally available; Posit Assistant fully replaces the legacy Positron Assistant. We know the names are similar, and Joe Cheng <a href="https://opensource.posit.co/blog/2026-06-11_history-of-posit-data-science-agents/" target="_blank" rel="noopener">recently walked through a bit of the story behind how our AI agent tools have evolved</a>.</p>
<p>This release also gives you finer control over AI in Positron. The new <a href="positron://settings/ai.enabled"><code>ai.enabled</code></a> setting turns off every Positron AI feature at once, and administrators can enforce it; <a href="positron://settings/notebook.ai.enabled"><code>notebook.ai.enabled</code></a> does the same for notebooks specifically. The set of language model providers keeps growing, with DeepSeek joining as an experimental provider and Microsoft Foundry reaching general availability. The configuration modal now shows every provider by default with preview and experimental badges.</p>
<h2 id="data-explorer">Data Explorer
</h2>
<p>The <a href="https://positron.posit.co/data-explorer" target="_blank" rel="noopener">Data Explorer</a> can now open Excel workbooks directly, with no code required, without first needing to load it via Python or R. Sort, filter, and profile columns, switch between worksheets, and toggle whether the first row holds column names. An <strong>Open in Excel</strong> button opens the workbook in your native spreadsheet application.</p>
<p>This release broadens what you can open in the Data Explorer overall. Backed by a native DuckDB engine, it now also previews compressed CSV, TSV, and Parquet files. Also, a new <strong>Open in Data Explorer</strong> code action, from the editor lightbulb or <kbd>Cmd+.</kbd>, opens the data frame under your cursor in R, Python, and Quarto files, so you can jump straight from your code to exploring your data.</p>
<img src="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/open-in-data-explorer-code-action.gif" data-fig-align="center" data-fig-alt="The Open in Data Explorer code action in Positron, showing a lightbulb menu in a Python file with the option to open a data frame in the Data Explorer." />
<h2 id="r-language-intelligence">R language intelligence
</h2>
<p>Last release we introduced within-file symbol resolution for R; this release expands it across files. Go to Definition, Find References, and Rename Symbol now work across the packages and scripts in your workspace, and diagnostics and workspace symbols react to external file changes so your language intelligence stays in sync as your project evolves.</p>
<h2 id="whats-coming-next">What&rsquo;s coming next
</h2>
<ul>
<li>New in preview this release, Data Connections lets you browse the schemas, tables, views, and indexes of a database, open tables in the Data Explorer, and generate connection code. It currently supports DuckDB, PostgreSQL, and SQLite. Learn how to try it out and tell us what you think in the <a href="https://github.com/posit-dev/positron/discussions/14571" target="_blank" rel="noopener">Data Connections discussion post</a>: which databases and warehouses you need, whether the connection setup is clear, and anything confusing, missing, or broken.</li>
<li>We are looking forward to posit::conf(2026) in September, where our team will have several sessions on Positron. <a href="https://conf.posit.co/2026/" target="_blank" rel="noopener">Register now</a> to join us in person in Houston or virtually from anywhere in the world.</li>
</ul>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Tip</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co/download" target="_blank" rel="noopener">Download Positron</a> to try out the new features and improvements in this release!</p>
</div>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/featured.svg" length="88493" type="image/svg&#43;xml" />
    </item>
    <item>
      <title>mcptools 1.0.0</title>
      <link>https://opensource.posit.co/blog/2026-07-06_mcptools-1-0-0/</link>
      <pubDate>Mon, 06 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-06_mcptools-1-0-0/</guid>
      <dc:creator>Simon Couch</dc:creator><description><![CDATA[<p>The first major release of <a href="https://posit-dev.github.io/mcptools/" target="_blank" rel="noopener">mcptools</a>, an R SDK for the Model Context Protocol, is now on CRAN! This release includes several notable features: fetching tools from remote authenticated servers, deploying MCP servers on Posit Connect, and support for images and other rich content types.</p>
<p>To install the package, run:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;mcptools&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>To demo these new features, I&rsquo;ll deploy an R function that returns a picture to an MCP server on Posit Connect. Then, in another R session, I&rsquo;ll connect to that server and ask an ellmer chat to take a look at the picture and tell me what it sees.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">mcptools</span><span class="p">)</span></span></span></code></pre></div></div>
<h2 id="images-in-tool-results">Images in tool results
</h2>
<p>mcptools supports &ldquo;both directions&rdquo; of MCP. In one direction, users can deploy R functions as MCP servers. In the other direction, users can fetch tools from third-party MCP servers as R functions. <strong>mcptools now supports both serving and fetching tools that return images.</strong></p>
<p>As an example of a function that returns an image, let&rsquo;s consider a tool <code>fetch_reference_image()</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">tools</span> <span class="o">&lt;-</span> <span class="nf">list</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">fetch_reference_image</span> <span class="o">=</span> <span class="nf">tool</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="kr">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">content_image_url</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;https://simonpcouch.com/blog/2026-04-16-local-agents-2/featured.png&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="s">&#34;fetch_reference_image&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Fetch the reference image.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p>Running that function:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">tools</span><span class="o">$</span><span class="nf">fetch_reference_image</span><span class="p">()</span></span></span></code></pre></div></div>
<figure>
<img src="https://simonpcouch.com/blog/2026-04-16-local-agents-2/featured.png" alt="A brown and white Border Collie on a deck, looking attentively at the camera, with wire railings and blurred greenery in the background." />
<figcaption aria-hidden="true">A brown and white Border Collie on a deck, looking attentively at the camera, with wire railings and blurred greenery in the background.</figcaption>
</figure>
<p>Without MCP, I can ask a model to look at the image and tell me what it sees:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">ch</span> <span class="o">&lt;-</span> <span class="nf">chat_claude</span><span class="p">(</span><span class="s">&#34;Be brief.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">ch</span><span class="o">$</span><span class="nf">register_tool</span><span class="p">(</span><span class="n">tools[[1]]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">ch</span><span class="o">$</span><span class="nf">chat</span><span class="p">(</span><span class="s">&#34;What do you see in the reference image?&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; I can see a beautiful Border Collie dog in the reference</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; image. The dog has:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - A chocolate brown and white coat with distinctive</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   coloring</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - Amber/brown eyes</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - Alert, perked ears</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - A white blaze down the center of its face</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - A pink/brown nose</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - Long, fluffy fur typical of the breed</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; The dog appears to be outdoors, positioned near what</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; looks like a wooden post or railing with wire fencing</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; visible in the background. There&#39;s greenery and trees</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; visible in the blurred background. The dog has an</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; attentive, intelligent expression that&#39;s characteristic</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; of Border Collies.</span></span></span></code></pre></div></div>
<p>In the next two sections, I&rsquo;ll deploy this function on Posit Connect and then fetch it from a fresh R session so that future ellmer chats can fetch this same image.</p>
<h2 id="mcp-servers-on-posit-connect">MCP Servers on Posit Connect
</h2>
<p>mcptools has now adopted plumber2&rsquo;s <code>_server.yml</code> open standard. This means that, <strong>to deploy an MCP server on Posit Connect, you just need to add a <code>_server.yml</code> file in your project root</strong> that looks like this:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">engine</span><span class="p">:</span><span class="w"> </span><span class="l">mcptools</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">tools</span><span class="p">:</span><span class="w"> </span><span class="l">tools.R</span></span></span></code></pre></div></div>
<p><code>tools.R</code> (or whatever you choose to name the file) is a file that defines ellmer tools and returns them in a list. In my case, <code>tools.R</code> looks exactly like the code chunk defining <code>tools</code> above. I can then run <code>rsconnect::deployAPI(&quot;.&quot;, contentCategory = &quot;mcp&quot;)</code> to deploy the tools from <code>tools.R</code> as an authenticated MCP server on Posit Connect.</p>
<h2 id="fetch-tools-as-r-functions-from-authenticated-mcp-servers">Fetch tools as R functions from authenticated MCP servers
</h2>
<p>Perhaps the biggest gap in mcptools before this release was that <code>mcp_tools()</code> did not support remote, authenticated MCP servers. Up to this point, I had written in documentation that folks ought to use <code>npx mcp-remote</code>, which converts remote MCP servers (like Slack, Confluence, or really any of the most well-adopted third-party MCP servers) into local ones. That meant that, even though mcptools only implemented the local half of the protocol, mcptools users could connect to remotely hosted MCP servers.</p>
<p>This is undesirable for a few reasons. For one, mcptools should &ldquo;just work&rdquo; without users having to install software from sources other than CRAN. Further, installing code via <code>npx</code> is particularly problematic; the node package registry has been the source of <a href="https://www.axios.com/2026/03/31/north-korean-hackers-implicated-in-major-supply-chain-attack" target="_blank" rel="noopener">a</a> <a href="https://arstechnica.com/security/2026/06/dozens-of-red-hat-packages-backdoored-through-its-offical-npm-channel/" target="_blank" rel="noopener">number</a> <a href="https://www.theregister.com/cyber-crime/2026/05/18/shai-hulud-copycat-hits-another-npm-package/5242180" target="_blank" rel="noopener">of</a> <a href="https://www.stepsecurity.io/blog/mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem" target="_blank" rel="noopener">particularly</a> <a href="https://unit42.paloaltonetworks.com/monitoring-npm-supply-chain-attacks/" target="_blank" rel="noopener">concerning</a> supply chain attacks recently.</p>
<p><strong><code>mcp_tools()</code> now natively supports fetching tools from authenticated, third-party MCP servers.</strong> For example, a configuration that used to look like:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;mcpServers&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;connect&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;npx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;args&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;mcp-remote&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&lt;my_deployed_connect_listing&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;--header&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Authorization: Key ${CONNECT_API_KEY}&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>Now looks like:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;mcpServers&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;connect&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;url&#34;</span><span class="p">:</span> <span class="s2">&#34;&lt;my_deployed_connect_listing&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;headers&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;Authorization&#34;</span><span class="p">:</span> <span class="s2">&#34;Key ${CONNECT_API_KEY}&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>I&rsquo;ll save that latter configuration as <code>config.json</code>. Then, I&rsquo;ll provide it to <code>mcp_tools()</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">tools_fetched</span> <span class="o">&lt;-</span> <span class="nf">mcp_tools</span><span class="p">(</span><span class="n">config</span> <span class="o">=</span> <span class="s">&#34;config.json&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">class</span><span class="p">(</span><span class="n">tools_fetched[[1]]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] &#34;ellmer::ToolDef&#34; &#34;function&#34;        &#34;S7_object&#34;</span></span></span></code></pre></div></div>
<p>Now I can register the fetched tools with a chat in a new R session, and it has access to the same image:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">ch_new</span> <span class="o">&lt;-</span> <span class="nf">chat_claude</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">ch_new</span><span class="o">$</span><span class="nf">register_tools</span><span class="p">(</span><span class="n">tools_fetched</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">ch_new</span><span class="o">$</span><span class="nf">chat</span><span class="p">(</span><span class="s">&#34;What&#39;s in the reference image?&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; The reference image features a **Border Collie** dog.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Here are some details:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - **Coloring**: The dog has a striking **brown</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   (chocolate) and white** coat, with a distinctive white</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   stripe running down the center of its face.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - **Expression**: It has an alert and attentive look,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   with beautiful **amber/brown eyes**.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - **Tongue**: Its tongue is slightly visible, giving it a</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   cute appearance.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - **Setting**: The dog appears to be on a **deck or</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   porch**, with cable/wire railings visible, and a lush</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   green, leafy background suggesting an outdoor, wooded</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   area.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - **Accessories**: It appears to be wearing a small</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   **collar tag**.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Overall, it&#39;s a beautiful and expressive dog photo! 🐕</span></span></span></code></pre></div></div>
<p>Taken together, the changes in this release should allow R users to do much more with MCP! For a more complete list of changes in this release, see the package <a href="https://posit-dev.github.io/mcptools/news/index.html" target="_blank" rel="noopener">changelog</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-06_mcptools-1-0-0/featured.png" length="874368" type="image/png" />
    </item>
    <item>
      <title>posit::glimpse() Newsletter – July 2026</title>
      <link>https://opensource.posit.co/blog/2026-07-01_2026-07-glimpse/</link>
      <pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-01_2026-07-glimpse/</guid>
      <dc:creator>Isabella Velásquez</dc:creator><description><![CDATA[<blockquote>
<p>Welcome to our newsletter, posit::glimpse()!</p>
<p>If you&rsquo;re currently reading this on our blog, consider subscribing to Product Updates - Open Source on our <a href="https://posit.co/about/subscription-management" target="_blank" rel="noopener">subscription page</a> to receive this newsletter directly in your inbox.</p>
</blockquote>
<p>Welcome to the latest edition of the posit::glimpse() newsletter, the monthly roundup of open-source news for the Posit community. I have many updates for you from across Posit. As my wonderful colleague Kristin Bott stated, “<em>dang</em> this is a productive bunch of humans”.</p>
<p>The table of contents on the right can help you navigate through all the updates. As you scroll, it will open up to show you subcategories →</p>
<h2 id="announcements">Announcements
</h2>
<h3 id="positconf2026-is-happening-soon">posit::conf(2026) is happening soon!
</h3>
<p>Our annual conference, <a href="https://conf.posit.co/2026/" target="_blank" rel="noopener">posit::conf(2026)</a>, is happening September 14-16, and we would love to see you there, whether in Houston or online! Check out the <a href="https://posit.co/blog/posit-conf-2026-agenda-breakdown" target="_blank" rel="noopener">speaker lineup</a>, <a href="https://posit.co/blog/workshops-at-positconf2026" target="_blank" rel="noopener">workshop offerings</a>, and <a href="https://conf.posit.co/2026/registration/" target="_blank" rel="noopener">register for posit::conf here</a>.</p>
<p>Tidy Dev Day is happening on September 17, a unique opportunity to collaboratively tackle open-source issues and work directly alongside the very developers who build and maintain the tools you use every day. As <a href="https://thetidytrekker.com/post/vibe-conf-ing/posit_conf_2025" target="_blank" rel="noopener">Meghan Harris stated about last year’s event</a>, “Tidy Dev Day (TDD) gave me the PERFECT opportunity to explore this further in a low-stress, supportive environment.” <a href="https://opensource.posit.co/blog/2026-06-25_tidy-dev-day-2026/" target="_blank" rel="noopener">Learn more about Tidy Dev Day here</a>.</p>
<h3 id="weve-joined-the-jupyter-foundation">We’ve joined the Jupyter Foundation
</h3>
<p>We are proud to announce that we are deepening our commitment to the <a href="https://jupyter.org/" target="_blank" rel="noopener">Jupyter</a> ecosystem by becoming an official <a href="https://jupyterfoundation.org/" target="_blank" rel="noopener">Jupyter Foundation</a> Member!</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-25_posit-joins-jupyter-foundation/" target="_blank" rel="noopener">We’ve Joined the Jupyter Foundation announcement</a> blog post.</li>
</ul>
<h3 id="announcing-an-expanded-posit-academy">Announcing an expanded Posit Academy
</h3>
<p>We&rsquo;ve launched a new part of <a href="http://academy.posit.co/" target="_blank" rel="noopener">Posit Academy</a>: a free, open library of product courses, hands-on labs, and live workshops available to anyone.</p>
<ul>
<li>Learn more in the <a href="https://posit.co/blog/announcing-expanded-posit-academy" target="_blank" rel="noopener">Announcing an expanded Posit Academy</a> blog post.</li>
</ul>
<h3 id="introducing-the-posit-impact-awards">Introducing the Posit Impact Awards
</h3>
<p>Have a story to share? We just launched the Posit Impact Awards to recognize individuals and teams who used Posit to create measurable, meaningful change. Six winners will be selected, one per category, and each will receive a conference-only pass to posit::conf(2026).</p>
<ul>
<li><a href="https://docs.google.com/forms/d/e/1FAIpQLSfQrWnEQ_wlc5lhyn5BLgU0mvfWDXb1XSXhq9PoSERdWSZS3g/viewform" target="_blank" rel="noopener">Submit your nomination before July 20th.</a></li>
</ul>
<h2 id="key-product-updates-and-new-releases">Key product updates and new releases
</h2>
<h3 id="data-visualization-and-reporting">Data visualization and reporting
</h3>
<h4 id="ggsql-041">ggsql 0.4.1
</h4>
<p><a href="https://ggsql.org/" target="_blank" rel="noopener">ggsql</a> 0.4.1 introduces spatial plotting capabilities with database-backed geometry processing, supporting WKB format data, 21 map projections for cartographic accuracy, and a built-in Natural Earth world dataset for creating choropleth maps and geographic visualizations with backends like DuckDB spatial, PostGIS, and SpatiaLite.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-23_ggsql_0_4_1/" target="_blank" rel="noopener">ggsql 0.4.1: Spatial plotting and in-layer aggregation</a> blog post.</li>
</ul>
<h4 id="ask-more-of-your-dashboard-with-querychat-and-ggsql">Ask more of your dashboard with querychat and ggsql
</h4>
<p><a href="https://posit-dev.github.io/querychat/" target="_blank" rel="noopener">querychat</a> now supports ggsql-powered visualizations, enabling natural language data exploration in dashboards through SQL-only execution (no arbitrary code), with three pre-built tools for visualizing, querying, and filtering data reactively. The package works in both Python and R, integrates with <a href="https://shiny.posit.co/" target="_blank" rel="noopener">Shiny</a> dashboards, and supports Snowflake Semantic Models for business logic definitions.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/" target="_blank" rel="noopener">Ask more of your dashboard with querychat and ggsql</a> blog post.</li>
</ul>
<h4 id="great-tables-0220">Great Tables 0.22.0
</h4>
<p><a href="https://posit-dev.github.io/great-tables/" target="_blank" rel="noopener">Great Tables</a> v0.22.0 significantly expands Python table presentation capabilities with footnote support, group-wise and grand summary row calculations, column merging utilities for uncertainty and ranges, text transformation methods, value substitution helpers, duration and parts-per formatters, image export functionality via gtsave(), enhanced LaTeX rendering, and makes Pandas an optional dependency for Polars-only workflows. (impressive update!)</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-25_great-tables-0-22-0/" target="_blank" rel="noopener">Great Tables v0.22.0</a> blog post.</li>
</ul>
<h3 id="data-access">Data access
</h3>
<h4 id="dbplyr-260">dbplyr 2.6.0
</h4>
<p><a href="https://dbplyr.tidyverse.org/" target="_blank" rel="noopener">dbplyr</a> 2.6.0 introduces ADBC support via adbi for faster Arrow-based data transfer, JDBC support, new SQL dialect separation, and query composition functions.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-17_dbplyr-2-6-0/" target="_blank" rel="noopener">dbplyr 2.6.0</a> blog post.</li>
</ul>
<h4 id="webr-060">webR 0.6.0
</h4>
<p><a href="https://docs.r-wasm.org/webr/latest/" target="_blank" rel="noopener">webR</a> 0.6.0 upgrades to R 4.6.0 and adds async/await support for JavaScript Promises, curl and httr2 compatibility through WebSocket traffic proxying, modern Fortran fixes for expanded package support, and updated system libraries including OpenSSL 3.5.1 and Emscripten 5.0.7. The release powers interactive R experiences in Quarto Live and Shinylive.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-18_webr-0-6-0/" target="_blank" rel="noopener">webR 0.6.0</a> blog post.</li>
</ul>
<h3 id="developer-tools-and-ai">Developer tools and AI
</h3>
<h4 id="debrief-010">debrief 0.1.0
</h4>
<p>The <a href="https://r-lib.github.io/debrief/" target="_blank" rel="noopener">debrief</a> package converts profvis profiling output into text-based summaries designed for AI agents, enabling AI-assisted performance optimization by providing structured reports on hotspots, call trees, and memory allocations that AI systems can read and act upon.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-22_debrief-0-1-0/" target="_blank" rel="noopener">debrief 0.1.0</a> blog post.</li>
</ul>
<h4 id="pkgsite-010">pkgsite 0.1.0
</h4>
<p><a href="https://edgararuiz.github.io/pkgsite/" target="_blank" rel="noopener">pkgsite</a> 0.1.0 converts R package .Rd documentation files into <a href="https://quarto.org/" target="_blank" rel="noopener">Quarto</a> .qmd files, enabling custom documentation sites with Quarto’s freeze feature for local example rendering, unified R/Python documentation when combined with Quartodoc, and flexible template customization. The package is available on CRAN and provides an alternative to pkgdown.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-18_pkgsite-0-1-0/" target="_blank" rel="noopener">pkgsite 0.1.0</a> blog post.</li>
</ul>
<h4 id="watcher-020">watcher 0.2.0
</h4>
<p><a href="https://watcher.r-lib.org/" target="_blank" rel="noopener">Watcher</a> is a lightweight R package that watches files and directories for changes and reacts in the background. It’s quietly been the engine behind Shiny&rsquo;s auto-reload for the past year. With the CRAN release of 0.2.0, we&rsquo;re excited to introduce it as a general-purpose filesystem watcher for R developers.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-29_watcher-0-2-0/" target="_blank" rel="noopener">watcher 0.2.0</a> blog post.</li>
</ul>
<h3 id="development-environment">Development environment
</h3>
<h4 id="air-0100">Air 0.10.0
</h4>
<p><a href="https://posit-dev.github.io/air/" target="_blank" rel="noopener">Air</a> 0.10.0 introduces configurable assignment style enforcement allowing teams to standardize on arrow (<code>&lt;-</code>), equal (<code>=</code>), or preserve existing styles, along with enhanced IDE integrations for Positron and RStudio, multiple installation methods via PyPI and conda-forge, pre-commit hook support, stdin integration for editors, and shell completions.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/" target="_blank" rel="noopener">Air 0.10.0</a> blog post.</li>
</ul>
<h4 id="whats-new-in-positron">What’s new in Positron
</h4>
<p><a href="https://positron.posit.co/" target="_blank" rel="noopener">Positron</a>’s June release includes a lot of highly requested features:</p>
<ul>
<li>Inline output for Quarto (one of Positron’s most-requested features ever!)</li>
<li>Posit Assistant, the successor to Positron Assistant</li>
<li>Packages pane improvements</li>
<li>A more customizable interface</li>
</ul>
<p>For more, check out the <a href="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/" target="_blank" rel="noopener">Positron June Release Highlights</a> post and <a href="https://posit.co/positron-updates-signup" target="_blank" rel="noopener">subscribe to Positron emails</a>.</p>
<p>Did you know that many of the most upvoted RStudio feature requests are already implemented in Positron? Learn about ten of them in the <a href="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/" target="_blank" rel="noopener">RStudio’s Top Feature Requests … In Positron blog post</a>!</p>
<h3 id="machine-learning-and-modeling">Machine learning and modeling
</h3>
<h4 id="brulee-100">brulee 1.0.0
</h4>
<p><a href="https://brulee.tidymodels.org/" target="_blank" rel="noopener">brulee</a> 1.0.0 significantly expands tabular deep learning capabilities in R with five new model architectures, GPU support including Apple Silicon, 32-bit precision for improved performance, and enhanced numerical stability, all integrated with the tidymodels ecosystem.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-24_brulee-1-0-0/" target="_blank" rel="noopener">brulee 1.0.0</a> blog post.</li>
</ul>
<h4 id="catboost-support-in-tidymodels">CatBoost support in tidymodels
</h4>
<p>CatBoost gradient boosting support is now available in <a href="https://www.tidymodels.org/" target="_blank" rel="noopener">tidymodels</a> through the <code>boost_tree()</code> interface, providing access to CatBoost’s strong categorical feature handling with full tidymodels integration including hyperparameter tuning, cross-validation, efficient submodel optimization, and orbital package support for SQL generation and in-database predictions.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-25_catboost-tidymodels/" target="_blank" rel="noopener">CatBoost support in tidymodels</a> blog post.</li>
</ul>
<h4 id="tidyclust-030">tidyclust 0.3.0
</h4>
<p><a href="https://tidyclust.tidymodels.org/index.html" target="_blank" rel="noopener">tidyclust</a> 0.3.0 introduces three new clustering model families and achieves full integration with tidymodels by replacing tidyclust-specific functions with native tune package support.</p>
<ul>
<li>Learn more in the <a href="https://opensource.posit.co/blog/2026-06-15_tidyclust-0-3-0/" target="_blank" rel="noopener">tidyclust 0.3.0</a> blog post.</li>
</ul>
<h2 id="event-roundup">Event roundup
</h2>
<p>We were all over the world this month, discussing how to adopt new data tools, get better at old ones, and just loving being part of the community. If you want to learn more about where we were, or see where we’ll be next, check out our <a href="https://opensource.posit.co/events/" target="_blank" rel="noopener">event page</a>.</p>
<p>Watch the recordings from some of these events:</p>















  
  
  
  
  

  
  
  
  
  
    
  

  
  
  
  
  
    
  

  
  
  
  
  



<div class="grid grid-cols-2 gap-6 xl:gap-8 xl:gap-8 mb-6">
  
    <div>
      











  
  
  
  
  
















  
  
    
  











  
  





  
    
  









  
  






  





  



  

  

  

  

  

  

  

  

  

  

  

  

  

  

















  
    
  





  
    
  
























<div id="item-27ed749654bd2d6c044eb1d6fb3bf2e9" class="@container h-full">
  <a href="https://opensource.posit.co/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/" title="Neal Richardson - MCP, or not MCP | Pydata London 26"
     class="card-link flex flex-col @venti:flex-row rounded-xl  h-full">

      



      
      

    
    <div class="w-full @venti:w-1/2 aspect-[40/21] flex-shrink-0 flex items-center justify-center overflow-hidden rounded-xl">
      
        









  
  
    
    

    

    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @max-tall:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_2e4f2a3a58db4627.webp 1x,
                  /resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_3bea74d726538bdd.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_7524b87c91de63ad.jpg"
          srcset="/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_7524b87c91de63ad.jpg 1x,
                  /resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_e7f65162a2cb51e6.jpg 2x"
          alt="Neal Richardson - MCP, or not MCP | Pydata London 26"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @tall:@max-grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_3bea74d726538bdd.webp 1x,
                  /resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_168415f79709c6d1.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_e7f65162a2cb51e6.jpg"
          srcset="/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_e7f65162a2cb51e6.jpg 1x,
                  /resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_409671ed69280e4b.jpg 2x"
          alt="Neal Richardson - MCP, or not MCP | Pydata London 26"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_168415f79709c6d1.webp 1x,
                  /resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_3829c4c9d7cfb126.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_409671ed69280e4b.jpg"
          srcset="/resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_409671ed69280e4b.jpg 1x,
                  /resources/videos/2026-06-15_neal-richardson-mcp-or-not-mcp-pydata-london-26/thumbnail_hu_99b33ee74fa66f52.jpg 2x"
          alt="Neal Richardson - MCP, or not MCP | Pydata London 26"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
  
  


      
    </div>

    
    <div class="hidden gap-y-2 @nip:flex @venti:w-1/2 flex-auto flex-col p-3 @venti:pl-5 text-gray-700 mt-2">

      
      

      
      
      <div class="hidden @short:flex flex-wrap gap-x-2 items-center text-xs font-medium text-gray-500">
        
          <span>Jun 15, 2026</span>
          <span>|</span>
        
        
        
          <span>34 min</span>
          <span>|</span>
        
        
          <span>221 views</span>
          
        
        
      </div>
      

      
      <h3 class="m-0! mt-3 font-semibold text-sm @grande:text-lg @venti:text-3xl line-clamp-2 text-sm @tall:text-base @grande:text-lg @venti:text-3xl text-gray-600">Neal Richardson - MCP, or not MCP | Pydata London 26</h3>
      

      
        <p class="m-0! text-xs @short:text-sm @grande:text-md @venti:text-lg line-clamp-2 short@line-clamp-3 @tall:line-clamp-3 font-medium text-gray-600">Neal Richardson - MCP, or not MCP | Pydata London 26 \Model Context Protocol is a standard for defining tools that can be made available to LLMs and AI applications. There’s a lot of noise out there about what you should use to get the best results from AI, so in this talk, I will provide some guidance on when you should use MCP, and when you should reach for some other tool. I will describe cases where MCP is the right tool for the job, and when other things, like skills or other context files, are better. I will also devote attention to questions of security and authentication, which are important for MCP, and provide concrete examples of how MCP servers can be used to unlock agentic workflows while also strengthening data governance. This talk is intended for those who are interested in using LLMs for workflows involving data. No prior experience with MCP is required.
Outline:
Intro: how can I get data from this API into my Claude Code session? What is MCP? When should you use it, when should you use other tools Work through an example Sharing and deploying MCP servers, alternatives and best practices Optimizing your tools for best results www.pydata.org
PyData is an educational program of NumFOCUS, a 501(c)3 non-profit organization in the United States. PyData provides a forum for the international community of users and developers of data analysis tools to share ideas and learn from each other. The global PyData network promotes discussion of best practices, new approaches, and emerging technologies for data management, processing, analytics, and visualization. PyData communities approach data science using many languages, including (but not limited to) Python, Julia, and R.
PyData conferences aim to be accessible and community-driven, with novice to advanced level presentations. PyData tutorials and talks bring attendees the latest project features along with cutting-edge use cases.
00:00 Welcome! 00:10 Help us add time stamps or captions to this video! See the description for details.
Want to help add timestamps to our YouTube videos to help with discoverability? Find out more here: https://github.com/numfocus/YouTubeVideoTimestamps
</p>
      

      
        <div class="text-sm @grande:text-md">
          <div class="mt-2 flex flex-row gap-x-4 items-center"><div class="flex flex-row flex-shrink-0"><img 
          src="https://opensource.posit.co/people/neal-richardson/profile.jpg" 
          alt="Neal Richardson" 
          class="my-0! w-6 h-6 rounded-full object-cover ring-2 ring-white "
          style="z-index: 10;"
        ></div><div class="line-clamp-2 font-medium text-gray-600">Neal Richardson</div></div>
        </div>
      

      <div class="grow"></div>
      
        
          <div class="hidden @grande:flex flex-wrap gap-x-1 gap-y-4 py-2 overflow-hidden h-10 items-end">
            
              <span class="pill">
                Python
              </span>
            
              <span class="pill">
                Tutorial
              </span>
            
              <span class="pill">
                Education
              </span>
            
              <span class="pill">
                NumFOCUS
              </span>
            
              <span class="pill">
                PyData
              </span>
            
              <span class="pill">
                Opensource
              </span>
            
              <span class="pill">
                Learn
              </span>
            
              <span class="pill">
                Software
              </span>
            
              <span class="pill">
                Python 3
              </span>
            
              <span class="pill">
                Julia
              </span>
            
              <span class="pill">
                Coding
              </span>
            
              <span class="pill">
                Learn to Code
              </span>
            
              <span class="pill">
                How to Program
              </span>
            
              <span class="pill">
                Scientific Programming
              </span>
            
          </div>
        
      
    </div>
  </a>
</div>


    </div>
  
    <div>
      











  
  
  
  
  
















  
  
    
  











  
  





  
    
  









  
  






  





  
    
  
    
  



















  
    
  





  
    
  
























<div id="item-0bea2d9d122698cc4c259ae2a49344c9" class="@container h-full">
  <a href="https://opensource.posit.co/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/" title="Agents for Correct, Transparent, and Reproducible Data Analysis - Simon Couch &amp; Sara Altman"
     class="card-link flex flex-col @venti:flex-row rounded-xl  h-full">

      



      
      

    
    <div class="w-full @venti:w-1/2 aspect-[40/21] flex-shrink-0 flex items-center justify-center overflow-hidden rounded-xl">
      
        









  
  
    
    

    

    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @max-tall:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_b03c3320e4321048.webp 1x,
                  /resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_709131f576b34e9b.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_946b93830a7ce124.jpg"
          srcset="/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_946b93830a7ce124.jpg 1x,
                  /resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_6f49f3aa2116d34a.jpg 2x"
          alt="Agents for Correct, Transparent, and Reproducible Data Analysis - Simon Couch &amp; Sara Altman"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @tall:@max-grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_709131f576b34e9b.webp 1x,
                  /resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_7b164a87556be464.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_6f49f3aa2116d34a.jpg"
          srcset="/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_6f49f3aa2116d34a.jpg 1x,
                  /resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_2dcdb2aac4711fb1.jpg 2x"
          alt="Agents for Correct, Transparent, and Reproducible Data Analysis - Simon Couch &amp; Sara Altman"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_7b164a87556be464.webp 1x,
                  /resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_a0b0c1c85b31ff70.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_2dcdb2aac4711fb1.jpg"
          srcset="/resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_2dcdb2aac4711fb1.jpg 1x,
                  /resources/videos/2026-06-26_agents-for-correct-transparent-and-reproducible-data-analysis-simon-couch-sara-altman/thumbnail_hu_f7c8a43de74d743a.jpg 2x"
          alt="Agents for Correct, Transparent, and Reproducible Data Analysis - Simon Couch &amp; Sara Altman"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
  
  


      
    </div>

    
    <div class="hidden gap-y-2 @nip:flex @venti:w-1/2 flex-auto flex-col p-3 @venti:pl-5 text-gray-700 mt-2">

      
      

      
      
      <div class="hidden @short:flex flex-wrap gap-x-2 items-center text-xs font-medium text-gray-500">
        
          <span>Jun 26, 2026</span>
          <span>|</span>
        
        
        
          <span>47 min</span>
          <span>|</span>
        
        
          <span>29 views</span>
          
        
        
      </div>
      

      
      <h3 class="m-0! mt-3 font-semibold text-sm @grande:text-lg @venti:text-3xl line-clamp-2 text-sm @tall:text-base @grande:text-lg @venti:text-3xl text-gray-600">Agents for Correct, Transparent, and Reproducible Data Analysis - Simon Couch &amp; Sara Altman</h3>
      

      
        <p class="m-0! text-xs @short:text-sm @grande:text-md @venti:text-lg line-clamp-2 short@line-clamp-3 @tall:line-clamp-3 font-medium text-gray-600">Agents for Correct, Transparent, and Reproducible Data Analysis - Simon Couch &amp; Sara Altman (Posit)
Abstract: How do we build competent data analysis agents? Data analysis requires a willingness to pause, question conclusions, and dig into subtleties. Frontier LLMs, however, are optimized to push tasks toward completion, not to slow down when something seems off. This tendency works well for coding agents, where success is often verifiable. But for data analysis, verification is more complicated, and autonomous work by the agent can be at odds with the spirit of the discipline. Drawing on our experience building data analysis agents, we&rsquo;ll share evaluations that expose where LLM-driven analysis goes wrong and design patterns that keep analyses correct, transparent, and reproducible.
Resources mentioned in the session:
Presentation Slides: https://simonpcouch.github.io/gen-ai-pharma-26 Presentation GitHub repository: https://github.com/simonpcouch/gen-ai-pharma-26 bluffbench: https://github.com/simonpcouch/bluffbench Posit Assistant Terminal (TUI): https://posit-dev.github.io/assistant/docs/downloads/tui/ Posit AI Newsletter: https://opensource.posit.co/tags/ai-newsletter/ Speakers:
Simon Couch builds tools that make the work of data science more joyful and effective. As an engineer on the AI Core Team at Posit, his work spans coding agents, model evaluations, inference engineering, and next-edit-suggestion systems. Drawing on his background in statistics, Simon spent several years authoring and maintaining core packages in the open-source tidymodels framework—like stacks, broom, and infer — before shifting his focus to LLMs. He blogs about his work at simonpcouch.com. Simon authors the Posit AI Newsletter along with Sara Altman.
Sara Altman is a Senior Developer Advocate on the AI Core team at Posit, where she focuses on how AI can be effectively and responsibly used for data science. Previously, she helped build Posit Academy and taught data science and R at Stanford. Sara authors the Posit AI Newsletter along with Simon Couch.
Presented at the 2026 R/Pharma GenAI Day
</p>
      

      
        <div class="text-sm @grande:text-md">
          <div class="mt-2 flex flex-row gap-x-4 items-center"><div class="flex flex-row flex-shrink-0"><img 
          src="https://opensource.posit.co/people/sara-altman/profile.jpg" 
          alt="Sara Altman" 
          class="my-0! w-6 h-6 rounded-full object-cover ring-2 ring-white "
          style="z-index: 10;"
        ><img 
          src="https://opensource.posit.co/people/simon-couch/profile.jpg" 
          alt="Simon Couch" 
          class="my-0! w-6 h-6 rounded-full object-cover ring-2 ring-white -ml-2"
          style="z-index: 9;"
        ></div><div class="line-clamp-2 font-medium text-gray-600">Sara Altman,&nbsp;Simon Couch</div></div>
        </div>
      

      <div class="grow"></div>
      
        
          <div class="hidden @grande:flex flex-wrap gap-x-1 gap-y-4 py-2 overflow-hidden h-10 items-end">
            
              <span class="pill">
                infer
              </span>
            
              <span class="pill">
                tidymodels
              </span>
            
          </div>
        
      
    </div>
  </a>
</div>


    </div>
  
</div>


<h2 id="showcases-from-the-community">Showcases from the community
</h2>









  
  
    
  

  
  
    
  





  


<div class="grid gap-12 items-start mt-12 md:grid-cols-2 ">
  
  
    
    
    
      <div class="prose max-w-none "><p>Brilliant Earth turned their Marketing Mix Model into a <a href="https://streamlit.io/" target="_blank" rel="noopener">Streamlit</a> app deployed on <a href="https://posit.co/products/enterprise/connect" target="_blank" rel="noopener">Posit Connect</a> via the <a href="https://docs.posit.co/partnerships/snowflake/" target="_blank" rel="noopener">Snowflake Native App</a>, so their marketing team can dig into channel performance and run scenario planning on their own. One of the campaigns in the mix: their recent Ring Pop collaboration.</p>
<ul>
<li><a href="https://posit.co/about/customer-stories/brilliant-earth" target="_blank" rel="noopener">Check out the spotlight.</a></li>
</ul></div>
    
  
    
    
    
      <div class="prose max-w-none "><div class="not-prose"><figure>
      <img class="h-auto max-w-full rounded-lg"
        src="https://opensource.posit.co/blog/2026-07-01_2026-07-glimpse/images/image01.png"
        alt="Brilliant Earth logo with rings in the background." 
        loading="lazy"
      >
    </figure></div></div>
    
  
</div>










  
  
    
  

  
  
    
  





  


<div class="grid gap-12 items-start mt-12 md:grid-cols-2 ">
  
  
    
    
    
      <div class="prose max-w-none "><div class="not-prose"><figure>
      <img class="h-auto max-w-full rounded-lg"
        src="https://opensource.posit.co/blog/2026-07-01_2026-07-glimpse/images/image02.png"
        alt="Dashboard showing Lorcana market data analysis with multiple charts displaying price trends and forecasting visualizations" 
        loading="lazy"
      >
    </figure></div></div>
    
  
    
    
    
      <div class="prose max-w-none "><p><a href="https://www.linkedin.com/in/leo-ohyama-phd-52358387/" target="_blank" rel="noopener">Leo Ohyama</a> recently shared a fantastic Lorcana Market Data Analysis &amp; Forecasting dashboard that showcases the power of Python, R, Positron, and Quarto working together.</p>
<p>Leo included detailed documentation in the GitHub repository, a great resource for anyone interested in market forecasting or multi-tool workflows. Thanks for sharing your work with the community, Leo!</p>
<ul>
<li><a href="https://lorecaster.ink/" target="_blank" rel="noopener">Lorcana dashboard</a></li>
<li><a href="https://github.com/leoohyama/lorcana" target="_blank" rel="noopener">Lorcana GitHub repo</a></li>
</ul>
</div>
    
  
</div>










  
  
    
  

  
  
    
  





  


<div class="grid gap-12 items-start mt-12 md:grid-cols-2 ">
  
  
    
    
    
      <div class="prose max-w-none "><p>We were recently joined by <a href="https://opensource.posit.co/people/thomas-lin-pedersen/" target="_blank" rel="noopener">Thomas Lin Pedersen</a> on the <a href="https://pos.it/dslab" target="_blank" rel="noopener">Data Science Lab</a>, where he introduced the new <a href="https://ggsql.org/" target="_blank" rel="noopener">ggsql</a> package.</p>
<p>(Almost) immediately after the DS Lab, <a href="https://www.linkedin.com/in/drspoulsen/" target="_blank" rel="noopener">Dylan Poulsen</a> wrote a blog post on exploring two years of swim data with ggsql! Dylan, we’re convinced you write blogs at the speed of ggsql.</p>
<ul>
<li><a href="https://dylanpoulsen.com/posts/2026-06-16-ggsql-swimming.html" target="_blank" rel="noopener">Exploring the New ggsql Package with Two Years of Swim Data</a> blog post</li>
</ul>
</div>
    
  
    
    
    
      <div class="prose max-w-none "><div class="not-prose"><figure>
      <img class="h-auto max-w-full rounded-lg"
        src="https://opensource.posit.co/blog/2026-07-01_2026-07-glimpse/images/image03.png"
        alt="Visualization created with ggsql showing swim data analysis with line charts tracking performance over time" 
        loading="lazy"
      >
    </figure></div></div>
    
  
</div>

<p>We usually find these projects on social media. If you’re on LinkedIn, be sure to follow and tag <a href="https://www.linkedin.com/showcase/posit-open-source/" target="_blank" rel="noopener">Posit Open Source</a> for us to share the amazing things you’re working on!</p>
<h2 id="whats-next">What’s next
</h2>
<p>We’re taking a short break in July before returning with more community hangouts!</p>
<ul>
<li>On July 21, Gina Reynolds will join the Data Science Lab (a fun, chill time with live code) to show we can extend ggplot2 by creating our own custom extensions. Register here: <a href="https://pos.it/dslab" target="_blank" rel="noopener">https://pos.it/dslab</a></li>
</ul>
<p>In the meantime, check out some past Data Science Lab episodes:</p>















  
  
  
  
  

  
  
  
  
  
    
  

  
  
  
  
  
    
  

  
  
  
  
  
    
  

  
  
  
  
  



<div class="grid grid-cols-3 gap-6 xl:gap-8 xl:gap-8 mb-6">
  
    <div>
      











  
  
  
  
  
















  
  
    
  











  
  





  
    
  









  
  






  





  
    
  
    
  
    
  
    
  
    
  



















  
    
  





  
    
  
























<div id="item-e1a7fb00aebe5239e2fb98a5bfb01eea" class="@container h-full">
  <a href="https://opensource.posit.co/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/" title="Async &amp; Parallel R with {mirai} | Charlie Gao | Data Science Lab"
     class="card-link flex flex-col @venti:flex-row rounded-xl  h-full">

      



      
      

    
    <div class="w-full @venti:w-1/2 aspect-[40/21] flex-shrink-0 flex items-center justify-center overflow-hidden rounded-xl">
      
        









  
  
    
    

    

    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @max-tall:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_c3d165a6da89ccf6.webp 1x,
                  /resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_e010d66820a12428.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_39e3051bf743594c.jpg"
          srcset="/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_39e3051bf743594c.jpg 1x,
                  /resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_8760612808811c9a.jpg 2x"
          alt="Async &amp; Parallel R with {mirai} | Charlie Gao | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @tall:@max-grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_e010d66820a12428.webp 1x,
                  /resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_1e8d89ec085c5bba.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_8760612808811c9a.jpg"
          srcset="/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_8760612808811c9a.jpg 1x,
                  /resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_ed5d4972ccad3c7.jpg 2x"
          alt="Async &amp; Parallel R with {mirai} | Charlie Gao | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_1e8d89ec085c5bba.webp 1x,
                  /resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_897267d62d43465d.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_ed5d4972ccad3c7.jpg"
          srcset="/resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_ed5d4972ccad3c7.jpg 1x,
                  /resources/videos/2026-05-29_async-parallel-r-with-mirai-charlie-gao-data-science-lab/thumbnail_hu_6f4f6696051268a6.jpg 2x"
          alt="Async &amp; Parallel R with {mirai} | Charlie Gao | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
  
  


      
    </div>

    
    <div class="hidden gap-y-2 @nip:flex @venti:w-1/2 flex-auto flex-col p-3 @venti:pl-5 text-gray-700 mt-2">

      
      

      
      
      <div class="hidden @short:flex flex-wrap gap-x-2 items-center text-xs font-medium text-gray-500">
        
          <span>May 29, 2026</span>
          <span>|</span>
        
        
        
          <span>57 min</span>
          <span>|</span>
        
        
          <span>1.2k views</span>
          
        
        
      </div>
      

      
      <h3 class="m-0! mt-3 font-semibold text-sm @grande:text-lg @venti:text-3xl line-clamp-2 text-sm @tall:text-base @grande:text-lg @venti:text-3xl text-gray-600">Async &amp; Parallel R with {mirai} | Charlie Gao | Data Science Lab</h3>
      

      
        <p class="m-0! text-xs @short:text-sm @grande:text-md @venti:text-lg line-clamp-2 short@line-clamp-3 @tall:line-clamp-3 font-medium text-gray-600">The Data Science Lab is a live weekly call. Register at pos.it/dslab! Discord invites go out each week on lives calls. We&rsquo;d love to have you!
The Lab is an open, messy space for learning and asking questions. Think of it like pair coding with a friend or two. Learn something new, and share what you know to help others grow.
On this call, Libby Heeren is joined by Charlie Gao, who walks through async and parallel programming using the mirai package for R. Charlie is the author of {mirai} and {mori}!
Charlie demonstrates how mirai lets you run R code in parallel across multiple cores or even distributed across remote machines. He covers the key differences between parallel and async programming, shows how to set up worker daemons (said like &ldquo;demons&rdquo;), scale resources dynamically, and connect to remote machines via SSH. Whether you&rsquo;re running long computations, training models, or building Shiny apps, mirai helps you make the most of your computing resources without blocking your main R session:)
Hosting crew from Posit: Libby Heeren, Isabella Velasquez
Charlie Gao&rsquo;s GitHub: https://github.com/shikokuchuo Charlie Gao&rsquo;s Bluesky: https://bsky.app/profile/shikokuchuo.net Charlie Gao&rsquo;s LinkedIn: https://www.linkedin.com/in/charliegao/ Charlie Gao&rsquo;s Mastodon: https://fosstodon.org/@shikokuchuo
Resources mentioned in the video and chat: mirai package website: https://mirai.r-lib.org/ mirai GitHub repository: https://github.com/r-lib/mirai AskDeepSeek chatbot for mirai documentation: https://mirai.r-lib.org/ (click &ldquo;ask deep wiki&rdquo; button) mirai - Promises (Shiny and Plumber): https://mirai.r-lib.org/articles/v02-promises.html mirai - Serialization: https://mirai.r-lib.org/articles/v03-serialization.html mirai - OpenTelemetry: https://mirai.r-lib.org/articles/v05-opentelemetry.html mirai stop_mirai function: https://mirai.r-lib.org/reference/stop_mirai.html mirai skill for Claude Code: https://github.com/r-lib/mirai/blob/main/.claude/skills/mirai/SKILL.md Plumber2 package: https://plumber2.posit.co/ Daemon (computing) on Wikipedia: https://en.wikipedia.org/wiki/Daemon_(computing)
► Subscribe to Our Channel Here: https://bit.ly/2TzgcOu Follow Us Here: Website: https://www.posit.co Hangout: https://pos.it/dsh The Lab: https://pos.it/dslab LinkedIn: https://www.linkedin.com/company/posit-software Bluesky: https://bsky.app/profile/posit.co
Thanks for hanging out with us!
Timestamps of Questions / Topics: 00:00 Introduction 03:33 &ldquo;What is async programming and how is it different from parallel?&rdquo; 06:57 AskDeepSeek chatbot for the Mirai package 09:40 Setting up the Positron IDE with activity bar on top 15:25 &ldquo;What was the motivation or need for developing Mirai?&rdquo; 22:10 Mirai map function for parallel processing 25:25 &ldquo;Do the contents of Mirai inherit definitions from the global environment?&rdquo; 29:00 &ldquo;What&rsquo;s the difference between Mirai versus promises and future?&rdquo; 31:50 Demonstrating sequential vs parallel processing 36:00 &ldquo;Can you use Mirai with HPC?&rdquo; 37:07 Dynamically scaling workers by adding and removing daemons 38:38 Setting up daemons with URLs for network connections 42:05 Launching workers over SSH to remote machines 43:09 SSH tunneling to connect workers without open ports 51:14 &ldquo;Does Mirai allow R to perform the same thing as NumPy?&rdquo; 51:54 &ldquo;Does Mirai ship with some kind of task viewing dashboard?&rdquo; 52:35 &ldquo;Can we use parallel::detectCores() to see how many daemons we can use?&rdquo; 53:58 &ldquo;Would parallel processing be more useful than async processing in typical data science work?&rdquo; 55:00 Mirai skill for Claude Code and AI agents
</p>
      

      
        <div class="text-sm @grande:text-md">
          <div class="mt-2 flex flex-row gap-x-4 items-center"><div class="flex flex-row flex-shrink-0"><img 
          src="https://opensource.posit.co/people/charlie-gao/profile.jpg" 
          alt="Charlie Gao" 
          class="my-0! w-6 h-6 rounded-full object-cover ring-2 ring-white "
          style="z-index: 10;"
        ></div><div class="line-clamp-2 font-medium text-gray-600">Charlie Gao</div></div>
        </div>
      

      <div class="grow"></div>
      
        
          <div class="hidden @grande:flex flex-wrap gap-x-1 gap-y-4 py-2 overflow-hidden h-10 items-end">
            
              <span class="pill">
                mirai
              </span>
            
              <span class="pill">
                mori
              </span>
            
              <span class="pill">
                plumber
              </span>
            
              <span class="pill">
                plumber2
              </span>
            
              <span class="pill">
                Positron
              </span>
            
          </div>
        
      
    </div>
  </a>
</div>


    </div>
  
    <div>
      











  
  
  
  
  
















  
  
    
  











  
  





  
    
  









  
  






  





  
    
  
    
  
    
  
    
  



















  
    
  





  
    
  
























<div id="item-451ed6564c115dcc7bdce93d95ee2802" class="@container h-full">
  <a href="https://opensource.posit.co/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/" title="Data dictionaries, parquet, &amp; Claude | Hadley Wickham | Data Science Lab"
     class="card-link flex flex-col @venti:flex-row rounded-xl  h-full">

      



      
      

    
    <div class="w-full @venti:w-1/2 aspect-[40/21] flex-shrink-0 flex items-center justify-center overflow-hidden rounded-xl">
      
        









  
  
    
    

    

    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @max-tall:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_a23a02bc126732e3.webp 1x,
                  /resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_36f7dc1a902e03a.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_7d73634aa10248d.jpg"
          srcset="/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_7d73634aa10248d.jpg 1x,
                  /resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_53383455a06a605f.jpg 2x"
          alt="Data dictionaries, parquet, &amp; Claude | Hadley Wickham | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @tall:@max-grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_36f7dc1a902e03a.webp 1x,
                  /resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_5a96a040aff01af5.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_53383455a06a605f.jpg"
          srcset="/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_53383455a06a605f.jpg 1x,
                  /resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_d35a1a3e9dce2346.jpg 2x"
          alt="Data dictionaries, parquet, &amp; Claude | Hadley Wickham | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_5a96a040aff01af5.webp 1x,
                  /resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_264773719908e6d9.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_d35a1a3e9dce2346.jpg"
          srcset="/resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_d35a1a3e9dce2346.jpg 1x,
                  /resources/videos/2026-06-23_data-dictionaries-parquet-claude-hadley-wickham-data-science-lab/thumbnail_hu_d52ea0ae3df67b45.jpg 2x"
          alt="Data dictionaries, parquet, &amp; Claude | Hadley Wickham | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
  
  


      
    </div>

    
    <div class="hidden gap-y-2 @nip:flex @venti:w-1/2 flex-auto flex-col p-3 @venti:pl-5 text-gray-700 mt-2">

      
      

      
      
      <div class="hidden @short:flex flex-wrap gap-x-2 items-center text-xs font-medium text-gray-500">
        
          <span>Jun 23, 2026</span>
          <span>|</span>
        
        
        
          <span>58 min</span>
          <span>|</span>
        
        
          <span>3.7k views</span>
          
        
        
      </div>
      

      
      <h3 class="m-0! mt-3 font-semibold text-sm @grande:text-lg @venti:text-3xl line-clamp-2 text-sm @tall:text-base @grande:text-lg @venti:text-3xl text-gray-600">Data dictionaries, parquet, &amp; Claude | Hadley Wickham | Data Science Lab</h3>
      

      
        <p class="m-0! text-xs @short:text-sm @grande:text-md @venti:text-lg line-clamp-2 short@line-clamp-3 @tall:line-clamp-3 font-medium text-gray-600">The Data Science Lab is a live weekly call. Register at pos.it/dslab! Discord invites go out each week on lives calls. We&rsquo;d love to have you!
The Lab is an open, messy space for learning and asking questions. Think of it like pair coding with a friend or two. Learn something new, and share what you know to help others grow.
On this call, Libby Heeren is joined by Hadley Wickham, who walks through using data dictionaries with Claude Code to clean and document datasets effectively.
Hadley demonstrates a workflow using three files: a data cleaning script, a data dictionary in YAML format, and the final cleaned data as a Parquet file. He shows how Claude Code and MCP REPL can help generate and maintain data dictionaries that document what you know about your data, making it easier for both humans and AI agents to work with your datasets. Using the NYC elevators dataset as an example, he walks through data cleaning tasks like normalizing whitespace, handling missing values, fixing date formats, and investigating geocoding issues - all while keeping the data dictionary, cleaning script, and Parquet file in sync through git.
Hosting crew from Posit: Libby Heeren, Isabella Velasquez
Hadley Wickham&rsquo;s GitHub: https://github.com/hadley Hadley Wickham&rsquo;s Bluesky: https://bsky.app/profile/hadley.nz Hadley Wickham&rsquo;s LinkedIn: https://www.linkedin.com/in/hadleywickham/
Resources mentioned in the video and chat: MCP REPL: https://github.com/posit-dev/mcp-repl Data Dictionary YAML Format Specification: https://github.com/hadley/data-dict.yaml Parquet files in R for Data Science: https://r4ds.hadley.nz/arrow.html#sec-parquet Elevators Dataset Used in Demo: https://github.com/EmilHvitfeldt/elevators Pointblank Package for Data Validation: https://posit-dev.github.io/pointblank/ Arrow R Book: https://arrowrbook.com/ Monaspace Font Family (with ligatures): https://monaspace.githubnext.com/ YAML Multiline Strings Reference: https://yaml-multiline.info/ UBC Course on Shiny with RAG and Parquet: https://ubc-mds.github.io/DSCI_532_vis-2_book/060-03-rag.html Tom Scott Video on Timezones: https://www.youtube.com/watch?v=-5wpm-gesOY Falsehoods Programmers Believe About Names: https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/ UTC Is Enough for Everyone Right: https://zachholman.com/talk/utc-is-enough-for-everyone-right Daniel Chen&rsquo;s Cherry Blossom Analysis: https://chendaniely.github.io/posts/2026/2026-03-30-yvr-cherry-blossoms-marathon/ Project Drawdown Climate Impact Explorer: https://drawdown.org/explorer Green Coding Bookdown Resource: https://bookdown.org/content/d1e53ac9-28ce-472f-bc2c-f499f18264a3/ IBM Green Coding Topics: https://www.ibm.com/think/topics/green-coding Secret Elevator in Central Park Article: https://undercovernyc.home.blog/2021/02/08/a-secret-elevator-hidden-in-central-park/ Artificial Cave Beneath Central Park: https://gizmodo.com/an-artificial-cave-200-beneath-central-park-with-micha-1446538828
► Subscribe to Our Channel Here: https://bit.ly/2TzgcOu Follow Us Here: Website: https://www.posit.co Hangout: https://pos.it/dsh The Lab: https://pos.it/dslab LinkedIn: https://www.linkedin.com/company/posit-software Bluesky: https://bsky.app/profile/posit.co
Thanks for hanging out with us!
Timestamps of Questions / Topics: 00:00 Introduction 03:38 &ldquo;Can you talk a little bit about what MCP is?&rdquo; 05:42 Introducing the elevators dataset 07:00 Creating initial data dictionary with Claude 09:03 &ldquo;Are there any cases where the CSV format is actually a better choice than Parquet?&rdquo; 09:50 &ldquo;What font do you use?&rdquo; 12:02 Adding context from the readme to the data dictionary 14:02 &ldquo;What is a good way to store data dictionaries along with datasets?&rdquo; 14:38 &ldquo;Is this particular data dict YAML format useful for projects with only one table of data?&rdquo; 17:45 &ldquo;Is Claude also going to decide when it&rsquo;s a good time to make a commit?&rdquo; 24:02 &ldquo;Does MCP REPL work on a Windows machine and can one use other AI LLM for example ChatGPT with it?&rdquo; 26:14 Converting date columns to proper date types 27:16 &ldquo;Do you see a meaningful distinction between data dicts and data contracts?&rdquo; 29:36 &ldquo;How do you view your approach to data dictionaries and the development of pointblank?&rdquo; 34:16 Eliminating placeholder values and using proper missing values 36:25 &ldquo;Could you show off a diff of a Parquet file?&rdquo; 38:25 Investigating geocodes and creating a map of elevators 40:20 Using a leaflet map to explore Central Park elevators 42:03 &ldquo;Which model is Hadley using?&rdquo; 43:55 Discussion of cost consciousness and environmental impact of LLMs 46:30 &ldquo;Is there a way to quantify environmental and electrical costs?&rdquo; 48:50 The mystery elevator in Central Park 54:23 &ldquo;How do you know this is actually faster or more productive than just writing the code?&rdquo; 55:45 The importance of deep knowledge of data in qualitative work
</p>
      

      
        <div class="text-sm @grande:text-md">
          <div class="mt-2 flex flex-row gap-x-4 items-center"><div class="flex flex-row flex-shrink-0"><img 
          src="https://opensource.posit.co/people/hadley-wickham/profile.jpg" 
          alt="Hadley Wickham" 
          class="my-0! w-6 h-6 rounded-full object-cover ring-2 ring-white "
          style="z-index: 10;"
        ></div><div class="line-clamp-2 font-medium text-gray-600">Hadley Wickham</div></div>
        </div>
      

      <div class="grow"></div>
      
        
          <div class="hidden @grande:flex flex-wrap gap-x-1 gap-y-4 py-2 overflow-hidden h-10 items-end">
            
              <span class="pill">
                bookdown
              </span>
            
              <span class="pill">
                bookdown.org
              </span>
            
              <span class="pill">
                leaflet
              </span>
            
              <span class="pill">
                pointblank
              </span>
            
          </div>
        
      
    </div>
  </a>
</div>


    </div>
  
    <div>
      











  
  
  
  
  
















  
  
    
  











  
  





  
    
  









  
  






  





  
    
  
    
  
    
  
    
  
    
  



















  
    
  





  
    
  
























<div id="item-4d9af617f6704fb38d6f46af20c8ee48" class="@container h-full">
  <a href="https://opensource.posit.co/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/" title="Using R package structure for data science projects | Kylie Ainslie | Data Science Lab"
     class="card-link flex flex-col @venti:flex-row rounded-xl  h-full">

      



      
      

    
    <div class="w-full @venti:w-1/2 aspect-[40/21] flex-shrink-0 flex items-center justify-center overflow-hidden rounded-xl">
      
        









  
  
    
    

    

    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @max-tall:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_b342729d310b0653.webp 1x,
                  /resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_2d213a05d6559bee.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_5eb9cd45b958caea.jpg"
          srcset="/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_5eb9cd45b958caea.jpg 1x,
                  /resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_2d34cd327bb21020.jpg 2x"
          alt="Using R package structure for data science projects | Kylie Ainslie | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @tall:@max-grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_2d213a05d6559bee.webp 1x,
                  /resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_7968fe6f5db9f34d.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_2d34cd327bb21020.jpg"
          srcset="/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_2d34cd327bb21020.jpg 1x,
                  /resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_7edb6814911cc29d.jpg 2x"
          alt="Using R package structure for data science projects | Kylie Ainslie | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
      
      

      
      
      

      
      
      
      

      <picture class="hidden @grande:block w-full h-full">
        <source
          type="image/webp"
          srcset="/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_7968fe6f5db9f34d.webp 1x,
                  /resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_74bb543c4243eb29.webp 2x" />
        <img
          src="https://opensource.posit.co/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_7edb6814911cc29d.jpg"
          srcset="/resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_7edb6814911cc29d.jpg 1x,
                  /resources/videos/2026-02-19_using-r-package-structure-for-data-science-projects-kylie-ainslie-data-science-lab/thumbnail_hu_b7f23d7c59f3ea7c.jpg 2x"
          alt="Using R package structure for data science projects | Kylie Ainslie | Data Science Lab"
          class="mx-auto h-full w-full object-cover"
          loading="lazy" />
      </picture>
    
  
  


      
    </div>

    
    <div class="hidden gap-y-2 @nip:flex @venti:w-1/2 flex-auto flex-col p-3 @venti:pl-5 text-gray-700 mt-2">

      
      

      
      
      <div class="hidden @short:flex flex-wrap gap-x-2 items-center text-xs font-medium text-gray-500">
        
          <span>Feb 19, 2026</span>
          <span>|</span>
        
        
        
          <span>55 min</span>
          <span>|</span>
        
        
          <span>1.7k views</span>
          
        
        
      </div>
      

      
      <h3 class="m-0! mt-3 font-semibold text-sm @grande:text-lg @venti:text-3xl line-clamp-2 text-sm @tall:text-base @grande:text-lg @venti:text-3xl text-gray-600">Using R package structure for data science projects | Kylie Ainslie | Data Science Lab</h3>
      

      
        <p class="m-0! text-xs @short:text-sm @grande:text-md @venti:text-lg line-clamp-2 short@line-clamp-3 @tall:line-clamp-3 font-medium text-gray-600">The Data Science Lab is a live weekly call. Register at pos.it/dslab! Discord invites go out each week on lives calls. We&rsquo;d love to have you!
The Lab is an open, messy space for learning and asking questions. Think of it like pair coding with a friend or two. Learn something new, and share what you know to help others grow.
On this call, Libby Heeren is joined by Kylie Ainslie who walks through how structuring data science projects as R packages provides a consistent framework that integrates documentation for you and facilitates collaboration with others by organizing things really well. Kylie says, &ldquo;I stumbled on using an R package structure to organize my projects a number of years ago and it has changed how I work in such a positive way that I want to share it with others! In a world where our attention is constantly being pulled in many directions, efficiency is crucial. Structuring projects as R packages is how I work more efficiently.&rdquo;
Hosting crew from Posit: Libby Heeren, Isabella Velasquez
Kylie&rsquo;s Bluesky: @kylieainslie.bsky.social Kylie&rsquo;s LinkedIn: https://www.linkedin.com/in/kylieainslie/ Kylie&rsquo;s Website: https://kylieainslie.github.io/ Kylie&rsquo;s GitHub: https://github.com/kylieainslie
Resources from the hosts and chat:
posit::conf(2026) call for talks: https://posit.co/blog/posit-conf-2026-call-for-talks/ Kylie&rsquo;s posit::conf(2025) talk: https://www.youtube.com/watch?v=YzIiWg4rySA {usethis} package: https://usethis.r-lib.org/ R Packages (2e) book: https://r-pkgs.org/ Paquetes de R (R Packages in Spanish): https://davidrsch.github.io/rpkgs-es/ {box} package: https://github.com/klmr/box extdata docs in Writing R Extensions: https://cran.r-project.org/doc/manuals/R-exts.html#Data-in-packages-1 Tan Ho&rsquo;s talk on NFL data: https://tanho.ca/talks/rsconf2022-github/ {rv} package: https://a2-ai.github.io/rv-docs/ Whether to Import or Depend: https://r-pkgs.org/dependencies-mindset-background.html#sec-dependencies-imports-vs-depends {pkgdown} package: https://pkgdown.r-lib.org/ Edgar Ruiz&rsquo;s {pkgsite} package: https://github.com/edgararuiz/pkgsite
Attendees shared examples of data packages in the chat! Here they are: https://kjhealy.github.io/nycdogs/ https://kjhealy.github.io/gssr/ https://github.com/deepshamenghani/richmondway https://github.com/kyleGrealis/nascaR.data https://github.com/ivelasq/leaidr
► Subscribe to Our Channel Here: https://bit.ly/2TzgcOu
Follow Us Here: Website: https://www.posit.co The Lab: https://pos.it/dslab Hangout: https://pos.it/dsh LinkedIn: https://www.linkedin.com/company/posit-software Bluesky: https://bsky.app/profile/posit.co
Thanks for learning with us!
Timestamps: 00:00 Introduction 06:17 Reviewing the disorganized project example 10:01 Creating the package structure using create_package 17:50 Organizing external data and scripts in the inst folder 22:55 Adding a README and License 29:06 &ldquo;What are the advantages to packaging a project?&rdquo; 33:35 Writing Roxygen2 documentation 36:06 &ldquo;Do you type return at the end of your functions?&rdquo; 41:55 Handling dependencies with use_package 43:53 &ldquo;Can you just use require(dplyr) at the top?&rdquo; 47:45 Setting up a pkgdown site 50:11 Creating vignettes 52:22 &ldquo;What is the role of the usethis package?&rdquo; 54:18 Loading the package with devtools::load_all
</p>
      

      
        <div class="text-sm @grande:text-md">
          <div class="mt-2 flex flex-row gap-x-4 items-center"><div class="flex flex-row flex-shrink-0"><img 
          src="https://opensource.posit.co/people/edgar-ruiz/profile.jpg" 
          alt="Edgar Ruiz" 
          class="my-0! w-6 h-6 rounded-full object-cover ring-2 ring-white "
          style="z-index: 10;"
        ></div><div class="line-clamp-2 font-medium text-gray-600">Edgar Ruiz</div></div>
        </div>
      

      <div class="grow"></div>
      
        
          <div class="hidden @grande:flex flex-wrap gap-x-1 gap-y-4 py-2 overflow-hidden h-10 items-end">
            
              <span class="pill">
                devtools
              </span>
            
              <span class="pill">
                dplyr
              </span>
            
              <span class="pill">
                pkgdown
              </span>
            
              <span class="pill">
                roxygen2
              </span>
            
              <span class="pill">
                usethis
              </span>
            
          </div>
        
      
    </div>
  </a>
</div>


    </div>
  
</div>


<p>I’m a real person, and I would love to know how to make the Glimpse newsletter better! Find me on <a href="https://www.linkedin.com/in/ivelasq/" target="_blank" rel="noopener">LinkedIn</a> and <a href="https://bsky.app/profile/ivelasq3.bsky.social" target="_blank" rel="noopener">Bluesky</a>, or email me at isabella [dot] velasquez [at] posit.co.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-01_2026-07-glimpse/thumbnail.jpg" length="68574" type="image/jpeg" />
    </item>
    <item>
      <title>watcher 0.2.0: filesystem watching for R, and the engine behind Shiny auto-reload</title>
      <link>https://opensource.posit.co/blog/2026-06-29_watcher-0-2-0/</link>
      <pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-29_watcher-0-2-0/</guid>
      <dc:creator>Charlie Gao</dc:creator><description><![CDATA[<p>If you&rsquo;ve ever saved a file in your Shiny app and watched the browser refresh on its own, you may have already used <a href="https://watcher.r-lib.org" target="_blank" rel="noopener">watcher</a> without knowing it.</p>
<p>watcher is a lightweight R package that watches files and directories for changes and reacts in the background. It shipped quietly last year as the engine behind Shiny&rsquo;s auto-reload, and until now that is mostly where it lived. watcher 0.2.0 is on CRAN, and we&rsquo;re taking this opportunity to introduce it as a general-purpose filesystem watcher for R developers to use.</p>
<p>Install it from CRAN:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;watcher&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<h2 id="the-engine-behind-shiny-auto-reload">The engine behind Shiny auto-reload
</h2>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-29_watcher-0-2-0/editor-and-app.png"
      alt="A code editor showing an app.R file on the left and the running Shiny app on the right: an &ldquo;Old Faithful Eruptions&rdquo; histogram with a &ldquo;Number of bins&rdquo; slider." 
      loading="lazy"
    >
  </figure></div>
</p>
<p>The inner loop of building a Shiny app is edit, save, switch to the browser, reload. Auto-reload removes the friction of the last two steps: you save, and the app reloads itself.</p>
<p>The easiest way to turn it on is Shiny&rsquo;s <a href="https://shiny.posit.co/r/reference/shiny/latest/devmode.html" target="_blank" rel="noopener">Developer Mode</a>, which flips on a handful of developer-friendly options for the session – auto-reload among them, alongside unminified JavaScript and full stack traces. Call <code>devmode()</code> once and run your app as usual:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">shiny</span><span class="o">::</span><span class="nf">devmode</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">shiny</span><span class="o">::</span><span class="nf">runApp</span><span class="p">(</span><span class="s">&#34;app.R&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Now every time you save a file in the app directory, the running app reloads to match:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-29_watcher-0-2-0/shiny-autoreload.gif"
      alt="A code editor and a running Shiny app side by side. As lines in app.R are edited and saved – the title, the bar colour, the number of bins – the app reloads to match, with no interaction in the browser." 
      loading="lazy"
    >
  </figure></div>
</p>
<p><em>Editing <code>app.R</code> on the left; the app on the right reloads on each save. No manual refresh or restart.</em></p>
<p>Under the hood, Developer Mode sets <code>shiny.autoreload = TRUE</code> (you can set that option directly if you&rsquo;d rather not switch on the rest of Developer Mode), which hands your app directory to watcher and starts it watching in the background. The instant you save a file, watcher&rsquo;s callback fires and Shiny pushes a reload to the browser.</p>
<p>Previously Shiny did this by polling: every few hundred milliseconds it re-listed the directory and compared modification times. That works, but the cost scales with the size of your project and the reload only ever happens on the next tick. watcher instead subscribes to the operating system&rsquo;s own filesystem-change notifications, so the reload fires on the save itself, and an idle app does no work at all.</p>
<p>For version 0.2.0, we&rsquo;ve simplified how the package installs from source. This means we can have it power auto-reload by default: the next release of Shiny will require watcher outright, so it&rsquo;s installed alongside Shiny with nothing to install by hand. (Shiny 1.14.0 uses watcher when it&rsquo;s present and falls back to polling otherwise – so for now it&rsquo;s worth installing watcher yourself).</p>
<h2 id="watcher-the-package">watcher, the package
</h2>
<p>This same machinery is available directly, outside of Shiny. <code>watcher()</code> returns an <a href="https://r6.r-lib.org" target="_blank" rel="noopener">R6</a> object that you can start and stop:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">watcher</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">dir.create</span><span class="p">(</span><span class="s">&#34;data&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">w</span> <span class="o">&lt;-</span> <span class="nf">watcher</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">path</span> <span class="o">=</span> <span class="s">&#34;data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">callback</span> <span class="o">=</span> <span class="nf">\</span><span class="p">(</span><span class="n">paths</span><span class="p">)</span> <span class="nf">cat</span><span class="p">(</span><span class="s">&#34;changed:&#34;</span><span class="p">,</span> <span class="n">paths</span><span class="p">,</span> <span class="s">&#34;\n&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">latency</span> <span class="o">=</span> <span class="m">0.5</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">w</span><span class="o">$</span><span class="nf">start</span><span class="p">()</span></span></span></code></pre></div></div>
<p>From now on, any change under <code>data/</code> calls your function back with the paths that changed:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">file.create</span><span class="p">(</span><span class="s">&#34;data/report.csv&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; changed: /home/you/project/data/report.csv</span></span></span></code></pre></div></div>
<p>Three arguments to <code>watcher()</code> cover most needs:</p>
<ul>
<li><strong><code>path</code></strong> – a file, a directory (watched recursively), or a vector of paths. Defaults to the working directory.</li>
<li><strong><code>callback</code></strong> – a function taking one argument: a character vector of the paths that changed. The default, <code>NULL</code>, simply writes the changed paths to <code>stdout</code>.</li>
<li><strong><code>latency</code></strong> – seconds to debounce events before reporting them. Defaults to 1.</li>
</ul>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">w</span><span class="o">$</span><span class="nf">stop</span><span class="p">()</span></span></span></code></pre></div></div>
<p>The returned object also has <code>$stop()</code>, <code>$is_running()</code> and <code>$get_path()</code> alongside <code>$start()</code>.</p>
<p>watcher runs on a background thread, but your callback runs on R&rsquo;s main thread, scheduled through <a href="https://later.r-lib.org" target="_blank" rel="noopener">later</a>. It fires when R is idle at the top level, whenever you call <code>later::run_now()</code>, or automatically inside an event loop such as Shiny&rsquo;s. This is what lets it slot easily into Shiny or plumber without blocking the session.</p>
<h2 id="put-it-to-work">Put it to work
</h2>
<p>watcher is deliberately small and general. Anything you want to happen when a file changes, you can wire to it:</p>
<ul>
<li>rebuild a report or re-render a document when its source or data changes</li>
<li>reprocess a directory as new files land in it</li>
<li>re-run tests or reload package code while you develop</li>
<li>reload configuration without restarting a long-running service</li>
</ul>
<p>The shape is always the same – give <code>watcher()</code> a path and a function:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">watcher</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">rebuild</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">paths</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nf">message</span><span class="p">(</span><span class="nf">format</span><span class="p">(</span><span class="nf">Sys.time</span><span class="p">()),</span> <span class="s">&#34;: &#34;</span><span class="p">,</span> <span class="nf">length</span><span class="p">(</span><span class="n">paths</span><span class="p">),</span> <span class="s">&#34; file(s) changed&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ... your render / test / reload step here ...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">w</span> <span class="o">&lt;-</span> <span class="nf">watcher</span><span class="p">(</span><span class="s">&#34;data&#34;</span><span class="p">,</span> <span class="n">rebuild</span><span class="p">,</span> <span class="n">latency</span> <span class="o">=</span> <span class="m">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">w</span><span class="o">$</span><span class="nf">start</span><span class="p">()</span></span></span></code></pre></div></div>
<p>The callback receives the paths that changed, so you can act on exactly what triggered it. File created, updated, removed and renamed events are monitored, for individual files or whole directory trees.</p>
<p>From here:</p>
<ul>
<li><a href="https://watcher.r-lib.org" target="_blank" rel="noopener">watcher package site</a> – reference and examples.</li>
<li><a href="https://github.com/r-lib/watcher" target="_blank" rel="noopener">r-lib/watcher</a> – source, issues, and feedback.</li>
<li>For Shiny, enable Developer Mode with <code>shiny::devmode()</code> (or set <code>options(shiny.autoreload = TRUE)</code> directly).</li>
</ul>
<h2 id="acknowledgments">Acknowledgments
</h2>
<p>watcher builds on <a href="https://github.com/emcrisostomo/fswatch" target="_blank" rel="noopener">libfswatch</a> by Enrico M. Crisostomo and Alan Dipert. This is a mature C++ filesystem-monitoring library, which uses the native, event-driven notification API on each platform.</p>
<p>Thanks go to Garrick Aden-Buie for wiring watcher into Shiny&rsquo;s auto-reload, and to everyone who has contributed issues, fixes, and feedback.</p>
<p>Questions and ideas are very welcome on GitHub: <a href="https://github.com/r-lib/watcher/issues" target="_blank" rel="noopener">r-lib/watcher</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-29_watcher-0-2-0/featured.jpg" length="298578" type="image/jpeg" />
    </item>
    <item>
      <title>Air 0.10.0</title>
      <link>https://opensource.posit.co/blog/2026-06-26_air-0-10-0/</link>
      <pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-26_air-0-10-0/</guid>
      <dc:creator>Davis Vaughan</dc:creator><description><![CDATA[<p>We&rsquo;re very excited to announce that Air 0.10.0 is out now!
<a href="https://github.com/posit-dev/air" target="_blank" rel="noopener">Air</a> is an extremely fast R code formatter, capable of styling entire projects in the blink of an eye.
We haven&rsquo;t done a release post in awhile, so this one will serve as a round up of everything from 0.8.2 to 0.10.0.</p>
<p>If you use <a href="https://positron.posit.co/" target="_blank" rel="noopener">Positron</a>, the Air Extension has probably already updated you to the new release!
Otherwise, follow one of Air&rsquo;s <a href="https://posit-dev.github.io/air/editors.html" target="_blank" rel="noopener">editor guides</a> to install the latest version for your editor.</p>
<p>To see every bug fix, check out our <a href="https://github.com/posit-dev/air/blob/main/CHANGELOG.md" target="_blank" rel="noopener">CLI changelog</a> and <a href="https://github.com/posit-dev/air/blob/main/editors/code/CHANGELOG.md" target="_blank" rel="noopener">extension changelog</a>.</p>
<h2 id="assignment-style-">Assignment style 😎
</h2>
<p>The headlining feature of 0.10.0 is a new <code>assignment-style</code> option, one of our <a href="https://github.com/posit-dev/air/issues/359" target="_blank" rel="noopener">top requests</a>!
This allows you to enforce <code>&lt;-</code> or <code>=</code> for assignment throughout your codebase.
Choose one of <code>&quot;arrow&quot;</code> for <code>&lt;-</code>, <code>&quot;equal&quot;</code> for <code>=</code>, or <code>&quot;preserve&quot;</code> to leave existing code as is.
For example, with <code>assignment-style = &quot;arrow&quot;</code>:</p>
<!-- panache-ignore-format-start -->
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># This</span>
</span></span><span class="line"><span class="cl"><span class="n">a</span> <span class="o">=</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="n">fn</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">z</span> <span class="o">&lt;-</span> <span class="n">x</span> <span class="o">+</span> <span class="n">y</span>
</span></span><span class="line"><span class="cl">  <span class="n">z</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Is standardized to</span>
</span></span><span class="line"><span class="cl"><span class="n">a</span> <span class="o">&lt;-</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="n">fn</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">z</span> <span class="o">&lt;-</span> <span class="n">x</span> <span class="o">+</span> <span class="n">y</span>
</span></span><span class="line"><span class="cl">  <span class="n">z</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<!-- panache-ignore-format-end -->
<p>This option defaults to enforcing <code>&lt;-</code> everywhere, which is what the <a href="https://style.tidyverse.org/syntax.html#assignment-1" target="_blank" rel="noopener">style guide</a> already recommended.
Note that this is a breaking change for Air, as the previous behavior didn&rsquo;t enforce any assignment style.
To revert to the old behavior, use <code>&quot;preserve&quot;</code>.</p>
<p>This feature is surprisingly sophisticated!
In R, you can always convert from <code>=</code> to <code>&lt;-</code> without changing the meaning of the code, but the reverse isn&rsquo;t true.
Consider the following:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">quote</span><span class="p">(</span><span class="n">x</span> <span class="o">&lt;-</span> <span class="m">1</span><span class="p">)</span></span></span></code></pre></div></div>
<p>This quotes the expression <code>x &lt;- 1</code> and returns it.
Blindly changing to <code>=</code> results in:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">quote</span><span class="p">(</span><span class="n">x</span> <span class="o">=</span> <span class="m">1</span><span class="p">)</span></span></span></code></pre></div></div>
<p>That&rsquo;s very different!
This sets an argument named <code>x</code> to <code>1</code>.
Trying to run this will result in an error since <code>quote()</code> doesn&rsquo;t have an <code>x</code> argument.
Air knows every case where <code>&lt;-</code> is allowed but <code>=</code> isn&rsquo;t, so you can rest assured that this option won&rsquo;t impact the meaning of your code in any way.</p>
<h2 id="positron-terminal-support">Positron terminal support
</h2>
<p>Positron&rsquo;s integration with Air has gotten even better!
When the Air extension activates, it now prepends the bundled Air executable to your integrated terminal&rsquo;s <code>PATH</code>.
This means:</p>
<ul>
<li>You can now run Air commands from Positron&rsquo;s terminal, even without installing the CLI separately.</li>
<li>Your terminal version of Air should now always match the version used by the editor itself.</li>
</ul>
<div class="w-full aspect-video">
      <video
        src="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/assets/which-air.mp4"
        class="w-full h-full object-contain"
        title="Air in your Positron terminal"
        controls></video>
    </div>
<p>This is particularly useful with agents like Claude Code, where you&rsquo;ll often want to tell your agent to run Air on any R files it touches (see <code>usethis::use_tidy_agents()</code> in the development version of usethis for a way to automatically add this to an <code>AGENTS.md</code>).</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/assets/air-claude.png"
      alt="Claude Code finding Air" 
      loading="lazy"
    >
  </figure></div>
</p>
<p>If you&rsquo;ve already installed the Air CLI separately, note that the bundled version will take precedence in Positron&rsquo;s terminals by default.
To force both the editor and the integrated terminal to use your externally installed version of Air, set <code>air.executableStrategy: &quot;environment&quot;</code>.
To opt-out of terminal support, set <code>air.addExecutableToTerminalPath: false</code>.</p>
<h2 id="rstudio-improvements">RStudio improvements
</h2>
<p>Thanks to <a href="https://github.com/kevinushey" target="_blank" rel="noopener">Kevin Ushey</a>, RStudio&rsquo;s support for Air is improving rapidly!</p>
<blockquote>
<p>Note that you&rsquo;ll need RStudio 2026.06.0 for this, which isn&rsquo;t out yet, but should be by the end of next week.</p>
</blockquote>
<p>RStudio now has &ldquo;native&rdquo; support for Air, rather than just being configurable as an External Formatter.
If you opt-in to Air, RStudio will now automatically download the latest version of the <code>air</code> binary the first time you save a file, making the whole experience much smoother.</p>
<div class="w-full aspect-video">
      <video
        src="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/assets/air-download.mp4"
        class="w-full h-full object-contain"
        title="Air auto downloading in RStudio"
        controls></video>
    </div>
<p>You can turn this on via <code>Tools -&gt; Global Options... -&gt; Code -&gt; Formatting</code>.
I recommend checking both <code>Use Air for code formatting</code> and <code>Reformat documents on save</code>.</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/assets/air-rstudio-settings.png"
      alt="RStudio settings for Air" 
      loading="lazy"
    >
  </figure></div>
</p>
<h2 id="lionstigersandbears-uv-pixi-and-mise-oh-my"><del>Lions, tigers, and bears</del> uv, pixi, and mise, oh my!
</h2>
<p>Air now lives on PyPi as <a href="https://pypi.org/project/air-formatter/" target="_blank" rel="noopener"><code>air-formatter</code></a> (sadly <code>air</code> was already taken), which means that you can now install Air&rsquo;s CLI via <a href="https://github.com/astral-sh/uv" target="_blank" rel="noopener">uv</a>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Install globally</span>
</span></span><span class="line"><span class="cl">uv tool install air-formatter
</span></span><span class="line"><span class="cl">air format path/to/file.R
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run one-off command</span>
</span></span><span class="line"><span class="cl">uvx --from air-formatter air format path/to/file.R</span></span></code></pre></div></div>
<p>It might sound a little absurd to put an R code formatting tool on Python&rsquo;s PyPI, but in a world where an increasingly large number of R users are also uv users, it&rsquo;s pretty darn convenient to be able to invoke it this way!</p>
<p>Additionally, Air is on <a href="https://github.com/conda-forge/air-feedstock" target="_blank" rel="noopener">conda-forge</a> thanks to <a href="https://github.com/salim-b" target="_blank" rel="noopener"><code>@salim-b</code></a>, which means you can also install it with <a href="https://pixi.prefix.dev/latest/" target="_blank" rel="noopener">pixi</a> and <a href="https://mise.jdx.dev/" target="_blank" rel="noopener">mise</a>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Add to a project</span>
</span></span><span class="line"><span class="cl">pixi add air
</span></span><span class="line"><span class="cl">mise use conda:air
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Install globally</span>
</span></span><span class="line"><span class="cl">pixi global install air
</span></span><span class="line"><span class="cl">mise use --global conda:air
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run one-off command</span>
</span></span><span class="line"><span class="cl">pixi <span class="nb">exec</span> air format path/to/my/script.R
</span></span><span class="line"><span class="cl">mise <span class="nb">exec</span> conda:air -- air format path/to/my/script.R</span></span></code></pre></div></div>
<h2 id="pre-commit-support">pre-commit support
</h2>
<p>Air now has <a href="https://pre-commit.com/" target="_blank" rel="noopener">pre-commit</a> and <a href="https://prek.j178.dev/" target="_blank" rel="noopener">prek</a> support via <a href="https://github.com/posit-dev/air-pre-commit" target="_blank" rel="noopener">posit-dev/air-pre-commit</a>.
To run Air on changed R files before every commit, add the following to your <code>.pre-commit-config.yaml</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">repos</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="nt">repo</span><span class="p">:</span><span class="w"> </span><span class="l">https://github.com/posit-dev/air-pre-commit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Air version</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">rev</span><span class="p">:</span><span class="w"> </span><span class="m">0.10.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">hooks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Run the formatter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">air-format</span></span></span></code></pre></div></div>
<h2 id="stdin-support">stdin support
</h2>
<p>Air&rsquo;s CLI now has <a href="https://posit-dev.github.io/air/cli.html#stdin" target="_blank" rel="noopener">stdin support</a>!
You can activate this with the new <code>--stdin-file-path</code> option like so:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat R/across.R <span class="p">|</span> air format --stdin-file-path R/across.R</span></span></code></pre></div></div>
<p><code>air</code> will receive the contents of <code>cat R/across.R</code> on stdin, format it, and then emit the formatted result back out on stdout.</p>
<p>You typically won&rsquo;t call this directly, but it&rsquo;s very useful for editors and IDEs without full language server support.
For example, with <a href="https://zed.dev/" target="_blank" rel="noopener">Zed</a> you can configure Air as an external formatter with the following setup:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;formatter&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;external&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;air&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;arguments&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;format&#34;</span><span class="p">,</span> <span class="s2">&#34;--stdin-file-path&#34;</span><span class="p">,</span> <span class="s2">&#34;{buffer_path}&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>This allows Zed to invoke Air on every file save.</p>
<blockquote>
<p>Note that Zed does have full language server support, and it&rsquo;s recommended to use our <a href="https://posit-dev.github.io/air/editor-zed.html" target="_blank" rel="noopener">Air extension for Zed</a> instead, but it makes for a nice example anyways!</p>
</blockquote>
<p>Invoking Air via stdin can also be useful in some embedded contexts, such as inside <a href="https://github.com/eitsupi/arf" target="_blank" rel="noopener">arf</a>, an R console.</p>
<h2 id="shell-completions">Shell completions
</h2>
<p>Thanks to <a href="https://github.com/salim-b" target="_blank" rel="noopener"><code>@salim-b</code></a>, Air can now generate shell completions for itself via a new <code>air generate-shell-completion &lt;shell&gt;</code> command, where <code>&lt;shell&gt;</code> can be one of <code>zsh</code>, <code>bash</code>, <code>powershell</code>, <code>fish</code>, or <code>elvish</code>.
Read the <a href="https://posit-dev.github.io/air/cli.html#shell-completions" target="_blank" rel="noopener">documentation</a> for how to set this up for your specific shell.
With shell completions set up, you can <code>Tab</code> your way through <code>air</code> commands at the command line:</p>
<div class="w-full aspect-video">
      <video
        src="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/assets/air-completions.mp4"
        class="w-full h-full object-contain"
        title="Air shell completions"
        controls></video>
    </div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/featured.jpg" length="638589" type="image/jpeg" />
    </item>
    <item>
      <title>CatBoost support in tidymodels</title>
      <link>https://opensource.posit.co/blog/2026-06-25_catboost-tidymodels/</link>
      <pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-25_catboost-tidymodels/</guid>
      <dc:creator>Emil Hvitfeldt</dc:creator><description><![CDATA[<p><a href="https://catboost.ai/" target="_blank" rel="noopener">CatBoost</a> is a very popular and high-quality gradient boosting library.
With the latest releases of <a href="https://bonsai.tidymodels.org/" target="_blank" rel="noopener">bonsai</a> and <a href="https://parsnip.tidymodels.org/" target="_blank" rel="noopener">parsnip</a>,
you can now train CatBoost models from R using the same tidymodels interface you already use for xgboost, LightGBM, and the rest of the <code>boost_tree()</code> family.</p>
<h2 id="installing-catboost">Installing CatBoost
</h2>
<p>The one wrinkle is installation.
The CatBoost R package is not on CRAN,
so you can&rsquo;t reach for <code>install.packages(&quot;catboost&quot;)</code> directly.</p>
<p>Grab the URL for your platform from the <a href="https://catboost.ai/docs/en/installation/r-installation-binary-installation" target="_blank" rel="noopener">CatBoost R installation guide</a> and install it with the remotes package.
For example, on an Apple Silicon or Intel Mac:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;remotes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">remotes</span><span class="o">::</span><span class="nf">install_url</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;https://github.com/catboost/catboost/releases/download/v1.2.10/catboost-R-darwin-universal2-1.2.10.tgz&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">INSTALL_opts</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;--no-multiarch&#34;</span><span class="p">,</span> <span class="s">&#34;--no-test-load&#34;</span><span class="p">,</span> <span class="s">&#34;--no-staged-install&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p>Swap in the release version, operating system, and architecture that match your setup.
The guide lists the full URL pattern and the binaries available for each release.</p>
<p>Once CatBoost itself is installed,
the tidymodels packages is just the usual packages:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># install.packages(&#34;pak&#34;)</span>
</span></span><span class="line"><span class="cl"><span class="n">pak</span><span class="o">::</span><span class="nf">pak</span><span class="p">(</span><span class="nf">c</span><span class="p">(</span><span class="s">&#34;tidymodels&#34;</span><span class="p">,</span> <span class="s">&#34;bonsai&#34;</span><span class="p">))</span></span></span></code></pre></div></div>
<p>You&rsquo;ll need <strong>bonsai 0.4.1</strong> (or later) and <strong>parsnip 1.4.0</strong> (or later),
which is where the CatBoost engine landed and got polished.</p>
<h2 id="fitting-a-catboost-model">Fitting a CatBoost model
</h2>
<p>CatBoost is supported as an engine for []<code>boost_tree()</code>](<a href="https://parsnip.tidymodels.org/reference/details_boost_tree_catboost.html%29" target="_blank" rel="noopener">https://parsnip.tidymodels.org/reference/details_boost_tree_catboost.html)</a>.
Loading bonsai registers the engine,
and from there it behaves like any other parsnip model spec:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">tidymodels</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bonsai</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cat_spec</span> <span class="o">&lt;-</span>
</span></span><span class="line"><span class="cl">  <span class="nf">boost_tree</span><span class="p">(</span><span class="n">trees</span> <span class="o">=</span> <span class="m">500</span><span class="p">,</span> <span class="n">learn_rate</span> <span class="o">=</span> <span class="m">0.05</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">set_engine</span><span class="p">(</span><span class="s">&#34;catboost&#34;</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">set_mode</span><span class="p">(</span><span class="s">&#34;regression&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cat_fit</span> <span class="o">&lt;-</span> <span class="nf">fit</span><span class="p">(</span><span class="n">cat_spec</span><span class="p">,</span> <span class="n">mpg</span> <span class="o">~</span> <span class="n">.,</span> <span class="n">data</span> <span class="o">=</span> <span class="n">mtcars</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">cat_fit</span></span></span></code></pre></div></div>
<pre><code>parsnip model object

CatBoost model (500 trees)
Loss function: RMSE
Fit to 10 feature(s)
</code></pre>
<h2 id="tuning">Tuning
</h2>
<p>The CatBoost engine supports the standard <code>boost_tree()</code> tuning parameters,
and the recent releases made tuning both faster and more correct.</p>
<p>A typical tuning setup looks like this:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">cat_spec</span> <span class="o">&lt;-</span>
</span></span><span class="line"><span class="cl">  <span class="nf">boost_tree</span><span class="p">(</span><span class="n">trees</span> <span class="o">=</span> <span class="nf">tune</span><span class="p">(),</span> <span class="n">learn_rate</span> <span class="o">=</span> <span class="nf">tune</span><span class="p">())</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">set_engine</span><span class="p">(</span><span class="s">&#34;catboost&#34;</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">set_mode</span><span class="p">(</span><span class="s">&#34;regression&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cat_wf</span> <span class="o">&lt;-</span> <span class="nf">workflow</span><span class="p">(</span><span class="n">mpg</span> <span class="o">~</span> <span class="n">.,</span> <span class="n">cat_spec</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">set.seed</span><span class="p">(</span><span class="m">123</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">folds</span> <span class="o">&lt;-</span> <span class="nf">vfold_cv</span><span class="p">(</span><span class="n">mtcars</span><span class="p">,</span> <span class="n">v</span> <span class="o">=</span> <span class="m">5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">tune_res</span> <span class="o">&lt;-</span> <span class="nf">tune_grid</span><span class="p">(</span><span class="n">cat_wf</span><span class="p">,</span> <span class="n">resamples</span> <span class="o">=</span> <span class="n">folds</span><span class="p">,</span> <span class="n">grid</span> <span class="o">=</span> <span class="m">20</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">show_best</span><span class="p">(</span><span class="n">tune_res</span><span class="p">,</span> <span class="n">metric</span> <span class="o">=</span> <span class="s">&#34;rmse&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code># A tibble: 5 × 8
  trees learn_rate .metric .estimator  mean     n std_err .config         
  &lt;int&gt;      &lt;dbl&gt; &lt;chr&gt;   &lt;chr&gt;      &lt;dbl&gt; &lt;int&gt;   &lt;dbl&gt; &lt;chr&gt;           
1  1473     0.0379 rmse    standard    2.76     5   0.419 pre0_mod15_post0
2   527     0.0513 rmse    standard    2.81     5   0.443 pre0_mod06_post0
3  1053     0.0941 rmse    standard    2.83     5   0.447 pre0_mod11_post0
4   211     0.127  rmse    standard    2.84     5   0.400 pre0_mod03_post0
5   632     0.234  rmse    standard    2.86     5   0.480 pre0_mod07_post0
</code></pre>
<p>Thanks to the <a href="https://parsnip.tidymodels.org/articles/Submodels.html" target="_blank" rel="noopener">submodel trick</a>
tuning <code>trees</code> doesn&rsquo;t require refitting the model from scratch at every candidate value.</p>
<h2 id="orbital-support">orbital support
</h2>
<p>CatBoost models also work with <a href="https://orbital.tidymodels.org/" target="_blank" rel="noopener">orbital</a>,
allowing you to turn your fitted catboost model into SQL and run predictions directly inside a database.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">orbital</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cat_spec</span> <span class="o">&lt;-</span>
</span></span><span class="line"><span class="cl">  <span class="nf">boost_tree</span><span class="p">(</span><span class="n">trees</span> <span class="o">=</span> <span class="m">50</span><span class="p">,</span> <span class="n">learn_rate</span> <span class="o">=</span> <span class="m">0.05</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">set_engine</span><span class="p">(</span><span class="s">&#34;catboost&#34;</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">set_mode</span><span class="p">(</span><span class="s">&#34;regression&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cat_wf</span> <span class="o">&lt;-</span> <span class="nf">workflow</span><span class="p">(</span><span class="n">mpg</span> <span class="o">~</span> <span class="n">.,</span> <span class="n">cat_spec</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">cat_fit</span> <span class="o">&lt;-</span> <span class="nf">fit</span><span class="p">(</span><span class="n">cat_wf</span><span class="p">,</span> <span class="n">data</span> <span class="o">=</span> <span class="n">mtcars</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">orbital_obj</span> <span class="o">&lt;-</span> <span class="nf">orbital</span><span class="p">(</span><span class="n">cat_fit</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">orbital_obj</span></span></span></code></pre></div></div>
<pre><code>── orbital Object ──────────────────────────────────────────────────────────────
• .pred = dplyr::case_when(cyl &lt;= 5 ~ dplyr::case_when(wt &lt;= 2.17 ~ dplyr ...
────────────────────────────────────────────────────────────────────────────────
1 equations in total.
</code></pre>
<p>From here you can predict in-database with <code>predict()</code> against a database connection,
or generate the SQL directly with <code>orbital_sql()</code>.
See the <a href="https://orbital.tidymodels.org/" target="_blank" rel="noopener">orbital documentation</a> for the full set of supported backends.</p>
<h2 id="wrapping-up">Wrapping up
</h2>
<p>CatBoost is a great addition to the gradient boosting options available in tidymodels,
especially if you work with categorical features or want a strong out-of-the-box model.</p>
<p>For the full details, see the <a href="https://bonsai.tidymodels.org/news/index.html" target="_blank" rel="noopener">bonsai changelog</a> and the <a href="https://parsnip.tidymodels.org/news/index.html#parsnip-140" target="_blank" rel="noopener">parsnip 1.4.0 release notes</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-25_catboost-tidymodels/featured.png" length="296364" type="image/png" />
    </item>
    <item>
      <title>Investing in the Future of Interactive Computing: We&#39;ve Joined the Jupyter Foundation</title>
      <link>https://opensource.posit.co/blog/2026-06-25_posit-joins-jupyter-foundation/</link>
      <pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-25_posit-joins-jupyter-foundation/</guid>
      <dc:creator>Isabella Velásquez</dc:creator><description><![CDATA[<p>At Posit, our mission is to empower data scientists and researchers with the best possible tools to explore, visualize, and share their work. For years, our team has relied on the <a href="https://jupyter.org/" target="_blank" rel="noopener">Jupyter</a> ecosystem, with Notebooks, IPython, JupyterLab, and JupyterHub all integrated across our product offerings and Jupyter kernels powering <a href="https://positron.posit.co/" target="_blank" rel="noopener">Positron</a>&rsquo;s R and Python support. Today, we are proud to announce that we are deepening our commitment to this community by becoming an official <a href="https://jupyterfoundation.org/" target="_blank" rel="noopener">Jupyter Foundation</a> Member!</p>
<p>Since its inception, <a href="https://jupyter.org/" target="_blank" rel="noopener">Project Jupyter</a> has grown into one of the world&rsquo;s most widely used open source ecosystems for interactive computing, powering breakthroughs in research, education, and industry. The Jupyter Foundation, hosted by the <a href="https://training.linuxfoundation.org/" target="_blank" rel="noopener">Linux Foundation</a>, was established to support the long-term sustainability of this work by bringing together organizations committed to investing in the project&rsquo;s future.</p>
<p>By joining as a Foundation Member, we are becoming active stewards of its future. Our membership helps fund the core infrastructure, the release engineering, and the community events that keep Jupyter at the cutting edge of scientific computing. This move reinforces our promise to the open source community: we will not only build on these tools, but we will also build up the people and processes that create them.</p>
<p>Open source is a shared resource, and it requires shared responsibility. We encourage our partners and peers in the industry to join us in supporting the foundations that make our work possible.</p>
<p>To learn more about our commitment to open research and technical innovation, visit our <a href="https://opensource.posit.co/about/posit/" target="_blank" rel="noopener">About Page</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-25_posit-joins-jupyter-foundation/featured.png" length="392627" type="image/png" />
    </item>
    <item>
      <title>Tidy Dev Day 2026</title>
      <link>https://opensource.posit.co/blog/2026-06-25_tidy-dev-day-2026/</link>
      <pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-25_tidy-dev-day-2026/</guid>
      <dc:creator>Mine Çetinkaya-Rundel</dc:creator><description><![CDATA[<p>Tidy Dev Day (TDD) is a community event where R developers and open source contributors gather to collaboratively tackle real issues across the tidyverse ecosystem.
We&rsquo;ll have a curated slate of issues ready to go &mdash; you can dive in solo or pair up with another attendee or with one of our experienced developers.
TDD 2026 will be held on September 17, 2026 9 am - 4 pm CDT in Houston, TX, immediately following <a href="https://conf.posit.co/2026/" target="_blank" rel="noopener">posit::conf(2026)</a>.</p>
<h2 id="who-should-come">Who should come?
</h2>
<p>You don&rsquo;t need an extensive development background.
You don&rsquo;t need to have solved issues before or sent a pull request.
What you do need: a GitHub account and some basic familiarity with git.</p>
<p>In the weeks before TDD, on September 3, 2026 at 12-1 pm EDT, we&rsquo;ll hold optional virtual office hours so you can get your R environment, git workflow, GitHub setup, and IDE all humming in harmony &mdash; walk in on the day feeling completely ready.
Participants who have signed up by that date will receive an email with the Zoom link for the office hours.</p>
<h2 id="how-to-sign-up">How to sign up
</h2>
<p>There is a token $10 cost for the day; we provide the venue, snacks, lunch, and a barista. If cost is an issue, please email <a href="mailto:hadley@posit.co">hadley@posit.co</a> and we&rsquo;ll work that out.
We only charge a small fee to encourage people to show up for the day.</p>
<p><a href="https://luma.com/pnjuw08h" target="_blank" rel="noopener">Get your ticket now!</a></p>
<p>Note: By using this link, you agree to Posit&rsquo;s Privacy Policy, available at <a href="https://posit.co/about/privacy-policy" target="_blank" rel="noopener">https://posit.co/about/privacy-policy</a>.
You also consent to using <a href="https://lu.ma" target="_blank" rel="noopener">https://lu.ma</a> for the purposes of processing your event attendance fees.</p>
<p><a href="https://opensource.posit.co/tags/tidyverse-dev-day">Previous Tidy Dev Days</a> have sold out quickly, so don&rsquo;t wait!</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-25_tidy-dev-day-2026/tdd-stickies.png" length="2725945" type="image/png" />
    </item>
    <item>
      <title>brulee 1.0.0</title>
      <link>https://opensource.posit.co/blog/2026-06-24_brulee-1-0-0/</link>
      <pubDate>Wed, 24 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-24_brulee-1-0-0/</guid>
      <dc:creator>Max Kuhn</dc:creator><description><![CDATA[<p>We are thrilled to release version 1.0.0 of the brulee package. brulee contains regression and classification models built using the torch framework. To install the package, use:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;brulee&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># or </span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">pak</span><span class="o">::</span><span class="nf">pak</span><span class="p">(</span><span class="s">&#34;brulee&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<h2 id="new-models">New Models
</h2>
<p>In the field of deep learning (DL), the term &ldquo;tabular&rdquo; refers to models that use common rectangular data sets, i.e., data in data frames or matrices. It specifically excludes raw images or unstructured text.</p>
<p>This release adds five new models:</p>
<ul>
<li><a href="https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C7&amp;q=Revisiting&#43;deep&#43;learning&#43;models&#43;for&#43;tabular&#43;data&amp;as_ylo=2021&amp;as_yhi=2021&amp;btnG=" target="_blank" rel="noopener">Residual networks</a> (ResNet) via <code>brulee_resnet()</code></li>
<li><a href="https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C7&amp;q=Regularization&#43;learning&#43;networks%3A&#43;Deep&#43;learning&#43;for&#43;tabular&#43;datasets.&amp;btnG=" target="_blank" rel="noopener">Regularization learning networks</a> (RLN) with <code>brulee_rln()</code></li>
<li><a href="https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C7&amp;q=&#43;AutoInt&#43;Automatic&#43;feature&#43;interaction&#43;learning&#43;via&#43;self&#43;attentive&#43;neural&#43;networks&amp;as_ylo=2019&amp;as_yhi=2019&amp;btnG=" target="_blank" rel="noopener">AutoInt</a> via <code>brulee_auto_int()</code></li>
<li><a href="https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C7&amp;q=&#43;Saint&#43;Improved&#43;neural&#43;networks&#43;for&#43;tabular&#43;data&#43;via&#43;row&#43;attention&#43;and&#43;contrastive&#43;pre&#43;training&amp;as_ylo=2021&amp;as_yhi=2021&amp;btnG=" target="_blank" rel="noopener">Self-Attention and Inter-sample Attention Transformer</a> (Saint) using <code>brulee_saint()</code></li>
<li><a href="https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C7&amp;q=Chronos-2%3A&#43;From&#43;univariate&#43;to&#43;universal&#43;forecasting&amp;btnG=" target="_blank" rel="noopener">Chronos2</a> foundational model for forecasting via <code>brulee_chronos()</code></li>
</ul>
<p>These models share common argument names and have the same conventions as existing brulee models, such as <code>brulee_mlp()</code>. Many of these models incorporate modern neural network techniques, such as batch normalization, residual connections, and/or attention mechanisms.</p>
<p>For applications with large amounts of data, these modern techniques can make an already difficult optimization problem easier. The attention mechanisms also allow the model to be more expressive, leading to better performance.</p>
<p>Here&rsquo;s an example using AutoInt. The data are simulated values with 10 predictors, a regression equation containing numerous interaction terms, and a true RMSE of 0.25.</p>
<p>The model was trained and evaluated on a random 10% of the data to determine early stopping. Training (using the ADAMw optimizer) stopped after 5 bad epochs and reverted to the estimates at epoch 7.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">brulee</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">tidymodels</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">set.seed</span><span class="p">(</span><span class="m">83</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">train_data</span> <span class="o">&lt;-</span> <span class="nf">sim_regression</span><span class="p">(</span><span class="m">2000</span><span class="p">,</span> <span class="n">method</span> <span class="o">=</span> <span class="s">&#34;hooker_2004&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">test_data</span> <span class="o">&lt;-</span> <span class="nf">sim_regression</span><span class="p">(</span><span class="m">2000</span><span class="p">,</span> <span class="n">method</span> <span class="o">=</span> <span class="s">&#34;hooker_2004&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Put the predictors on the same scale:</span>
</span></span><span class="line"><span class="cl"><span class="n">rec</span> <span class="o">&lt;-</span>
</span></span><span class="line"><span class="cl">  <span class="nf">recipe</span><span class="p">(</span><span class="n">outcome</span> <span class="o">~</span> <span class="n">.,</span> <span class="n">data</span> <span class="o">=</span> <span class="n">train_data</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">step_normalize</span><span class="p">(</span><span class="nf">all_numeric_predictors</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Set the R seed</span>
</span></span><span class="line"><span class="cl"><span class="nf">set.seed</span><span class="p">(</span><span class="m">13</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Set the torch seed (they don&#39;t need to be the same)</span>
</span></span><span class="line"><span class="cl"><span class="n">torch</span><span class="o">::</span><span class="nf">torch_manual_seed</span><span class="p">(</span><span class="m">13</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">autoint_fit</span> <span class="o">&lt;-</span>
</span></span><span class="line"><span class="cl">  <span class="nf">brulee_auto_int</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">rec</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">train_data</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">num_embedding</span> <span class="o">=</span> <span class="m">20</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">verbose</span> <span class="o">=</span> <span class="kc">TRUE</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span></span></span></code></pre></div></div>
<pre><code>epoch: 000, learn rate: 0.01, Loss (scaled): 1.06
epoch: 1, learn rate: 0.01, Loss (scaled): 0.179
epoch: 2, learn rate: 0.01, Loss (scaled): 0.144
epoch: 3, learn rate: 0.01, Loss (scaled): 0.12
epoch: 4, learn rate: 0.01, Loss (scaled): 0.103
epoch: 5, learn rate: 0.01, Loss (scaled): 0.115
epoch: 6, learn rate: 0.01, Loss (scaled): 0.102
epoch: 7, learn rate: 0.01, Loss (scaled): 0.101
epoch: 8, learn rate: 0.01, Loss (scaled): 0.109
epoch: 9, learn rate: 0.01, Loss (scaled): 0.101
epoch: 10, learn rate: 0.01, Loss (scaled): 0.108
epoch: 11, learn rate: 0.01, Loss (scaled): 0.112
epoch: 12, learn rate: 0.01, Loss (scaled): 0.116
</code></pre>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">autoplot</span><span class="p">(</span><span class="n">autoint_fit</span><span class="p">)</span></span></span></code></pre></div></div>
<img src="https://opensource.posit.co/blog/2026-06-24_brulee-1-0-0/index.markdown_strict_files/figure-markdown_strict/training-1.png" width="768" />
<p>One new addition to brulee is a collection of <code>summary()</code> methods that print layer dimensions and the corresponding number of parameters. This model is complex but, by deep learning standards, is small:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">summary</span><span class="p">(</span><span class="n">autoint_fit</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code>AutoInt architecture
inputs: 10 (0 categorical, 10 numeric) | output dim: 1

Embedding layer
  ContWeights(10 x 20)                200 params

Self-attention backbone (3 blocks, 2 heads)
  Linear(20 -&gt; 32)                    672 params
  MultiheadAttention(32, heads=2)   4,224 params
  MultiheadAttention(32, heads=2)   4,224 params
  MultiheadAttention(32, heads=2)   4,224 params
  + skip: Linear(20 -&gt; 32)            672 params
  ReLU                                  0 params
  output: 10 embeddings of dim 32 (flattened: 320)

Output head
  Linear(320 -&gt; 1)                    321 params

Total parameters: 14,537
</code></pre>
<p>How well does the model work on unseen data? Pretty well! The RMSE is fairly close to the best possible value.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">test_pred</span> <span class="o">&lt;-</span>
</span></span><span class="line"><span class="cl">  <span class="nf">predict</span><span class="p">(</span><span class="n">autoint_fit</span><span class="p">,</span> <span class="n">test_data</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">bind_cols</span><span class="p">(</span><span class="n">test_data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">test_pred</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">ggplot</span><span class="p">(</span><span class="nf">aes</span><span class="p">(</span><span class="n">outcome</span><span class="p">,</span> <span class="n">.pred</span><span class="p">))</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">  <span class="nf">geom_point</span><span class="p">()</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">  <span class="nf">geom_abline</span><span class="p">(</span><span class="n">col</span> <span class="o">=</span> <span class="s">&#34;#8FDA04FF&#34;</span><span class="p">,</span> <span class="n">lty</span> <span class="o">=</span> <span class="m">2</span><span class="p">,</span> <span class="n">linewidth</span> <span class="o">=</span> <span class="m">1</span><span class="p">)</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">  <span class="nf">geom_smooth</span><span class="p">(</span><span class="n">col</span> <span class="o">=</span> <span class="s">&#34;#D84D16FF&#34;</span><span class="p">,</span> <span class="n">se</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">)</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">  <span class="nf">coord_fixed</span><span class="p">(</span><span class="n">ratio</span> <span class="o">=</span> <span class="m">1</span><span class="p">)</span></span></span></code></pre></div></div>
<img src="https://opensource.posit.co/blog/2026-06-24_brulee-1-0-0/index.markdown_strict_files/figure-markdown_strict/evaluation-1.png" width="768" />
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">test_pred</span> <span class="o">|&gt;</span> <span class="nf">metrics</span><span class="p">(</span><span class="n">outcome</span><span class="p">,</span> <span class="n">.pred</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code># A tibble: 3 × 3
  .metric .estimator .estimate
  &lt;chr&gt;   &lt;chr&gt;          &lt;dbl&gt;
1 rmse    standard       0.316
2 rsq     standard       0.898
3 mae     standard       0.251
</code></pre>
<p>One important note: the Chronos model is a pretrained foundational model for forecasting. On its first use, it will download the model weights. These are about 200 MB in size and are cached locally, so this download should occur only once. This is similar to how our package for the <a href="https://opensource.posit.co/blog/2026-03-31_tabpfn-0-1-0">TabPFN foundational model</a> operates.</p>
<h2 id="numerical-changes">Numerical Changes
</h2>
<p>This version breaks backward compatibility with previous versions, but for good reasons.</p>
<p>The first is to switch from 64-bit precision to 32-bit. This is a common strategy (and many DL models go down to 16- or 8-bit precision). This should make models run faster and have a smaller memory footprint, but the main reason was to enable GPU computations on Apple machines. Note, however, that for the data set and model sizes shown in the example above, there is limited efficiency gain on the GPU. Since the computational load is relatively light (by DL standards), the GPU spends most of its time idle, so it isn&rsquo;t much faster than the CPU. With larger data sets and models like Saint or Chronos, there should be some time savings. Overall, though, there should be drastic memory savings when using the GPU compared to pure CPU computations.</p>
<p>We&rsquo;ve also made an effort to increase the numerical stability of these methods. This involves a more pervasive gradient clipping strategy and, for classification models, a more numerically stable implementation of the cross-entropy objective function.</p>
<p>One small improvement concerns reproducibility. torch has some inconsistencies in how random numbers are handled for GPU computations. When initializing parameters for these models, the random numbers are generated on the CPU and then moved to the GPU. This allows the model to start from the same place independently of where the subsequent computations occur.</p>
<h2 id="whats-next">What&rsquo;s Next
</h2>
<p>For brulee, the next big step is an R torch implementation of the <a href="https://scholar.google.com/scholar?hl=en&amp;as_sdt=0%2C7&amp;q=&#43;TabICLv2&#43;A&#43;better&#43;faster&#43;scalable&#43;and&#43;open&#43;tabular&#43;foundation&#43;model&amp;as_ylo=2026&amp;as_yhi=2026&amp;btnG=" target="_blank" rel="noopener">TabICL foundational model</a>. This shows great promise but has a large computational footprint (in terms of code and system resources). Like Chronos, it will also require downloading pretrained model weights. We have a working version but are conducting additional testing before we release it.</p>
<p>Outside of brulee, we&rsquo;re working on a parsnip interface to these models. Deep neural networks tend to have many tuning parameters, and many of them substantially affect model performance. The extension will include a function to make efficient space-filling designs for models with multiple hidden layers.</p>
<h2 id="thanks">Thanks
</h2>
<p>Banner image by <a href="https://unsplash.com/@lauraperuchi" target="_blank" rel="noopener">Laura Peruchi</a>.</p>
<p>Thanks to contributors <a href="https://github.com/edgararuiz" target="_blank" rel="noopener">@edgararuiz</a>, <a href="https://github.com/jeroenjanssens" target="_blank" rel="noopener">@jeroenjanssens</a>, <a href="https://github.com/lizelgreyling" target="_blank" rel="noopener">@lizelgreyling</a>, and <a href="https://github.com/Wander03" target="_blank" rel="noopener">@Wander03</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-24_brulee-1-0-0/brulee.jpeg" length="953553" type="image/jpeg" />
    </item>
    <item>
      <title>Introducing debrief: profiling summaries for AI agents</title>
      <link>https://opensource.posit.co/blog/2026-06-22_debrief-0-1-0/</link>
      <pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-22_debrief-0-1-0/</guid>
      <dc:creator>Emil Hvitfeldt</dc:creator><description><![CDATA[<p>While AI agents are getting better at writing code,
they sometimes struggle with making the code faster.
We have a number of tools at our disposal to do this,
but some of them are not made with AI agents in mind.
The <a href="https://profvis.r-lib.org/" target="_blank" rel="noopener">profvis</a> package is one such example.
The interactive flame graph it produces is fantastic for human eyes,
it allows us to easily see the broad picture while also drilling down into the details.
This is what started the idea for this new package; debrief.
Allowing the AI agent to read the profvis results as text,
thus letting it make more informed decisions about how to optimize the code.</p>
<p>debrief is on CRAN:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;debrief&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># or the development version:</span>
</span></span><span class="line"><span class="cl"><span class="n">pak</span><span class="o">::</span><span class="nf">pak</span><span class="p">(</span><span class="s">&#34;r-lib/debrief&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<h2 id="from-guessing-to-measuring">From guessing to measuring
</h2>
<p>Prompt an AI agent to make your R code faster,
and it will either declare that a bottleneck exists without evidence,
or try to use <code>Rprof()</code> directly.
We find that <code>profvis()</code> is better and easier to use than <code>Rprof()</code> directly,
as it provides a more user-friendly interface and visualizations.
But that interface is designed for human eyes,
not for an LLM reading a transcript.</p>
<p>This is where debrief comes in.
It turns the output of <code>profvis()</code> into a text-based summary that can be easily read and understood by AI agents.
Every function is prefixed <code>pv_</code> (for profvis) and returns text designed to be read top to bottom:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">debrief</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">p</span> <span class="o">&lt;-</span> <span class="n">profvis</span><span class="o">::</span><span class="nf">profvis</span><span class="p">(</span><span class="nf">slow_function</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="nf">pv_print_debrief</span><span class="p">(</span><span class="n">p</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ## PROFILING SUMMARY</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Total time: 190 ms (19 samples @ 10 ms interval)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Source references: available</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### TOP FUNCTIONS BY SELF-TIME</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    110 ms ( 57.9%)  paste</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     20 ms ( 10.5%)  &lt;GC&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  .bincode</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  any</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  anyDuplicated.default</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  apply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  rnorm</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  unlist</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### TOP FUNCTIONS BY TOTAL TIME</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    180 ms ( 94.7%)  FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    180 ms ( 94.7%)  lapply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    180 ms ( 94.7%)  process_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    140 ms ( 73.7%)  summarize_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    110 ms ( 57.9%)  paste</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     30 ms ( 15.8%)  clean_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     20 ms ( 10.5%)  &lt;GC&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     20 ms ( 10.5%)  apply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  .bincode</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  [.data.frame</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### HOT LINES (by self-time)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    120 ms ( 63.2%)  analysis.R:22</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                    list(</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  analysis.R:9</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                    x &lt;- rnorm(n)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### HOT CALL PATHS</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 110 ms (57.9%) - 11 samples:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     lapply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; process_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; summarize_data (analysis.R:5)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; paste (analysis.R:22)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 10 ms (5.3%) - 1 samples:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     base::tryCatch</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; any</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 10 ms (5.3%) - 1 samples:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     lapply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; process_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; clean_data (analysis.R:4)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; [.data.frame (analysis.R:15)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; anyDuplicated.default</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 10 ms (5.3%) - 1 samples:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     lapply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; process_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; clean_data (analysis.R:4)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; cut.default</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; .bincode</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 10 ms (5.3%) - 1 samples:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     lapply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; process_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; clean_data (analysis.R:4)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; scale.default</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; apply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; &lt;GC&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### MEMORY ALLOCATION (by function)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    51.28 MB paste</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    10.01 MB anyDuplicated.default</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     2.90 MB any</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     1.62 MB rnorm</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### MEMORY ALLOCATION (by line)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    51.28 MB analysis.R:22</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;             list(</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     1.62 MB analysis.R:9</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;             x &lt;- rnorm(n)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### Next steps</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; pv_focus(p, &#34;paste&#34;)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; pv_source_context(p, &#34;analysis.R&#34;)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; pv_suggestions(p)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; pv_help()</span></span></span></code></pre></div></div>
<p><code>pv_print_debrief()</code> is meant as the first entry point after profiling.
It gives a comprehensive overview of the profile with a number of sections.</p>
<ul>
<li>Top functions by self-time and total time</li>
<li>Hot lines and hot call paths</li>
<li>Memory allocation by function and by line</li>
<li>Next steps</li>
</ul>
<p>Both the hot lines and hot paths sections link back to source references when available,
Especially relevant if the bottleneck is happening in a common function name like <code>paste()</code> that is scattered across your package.
With this, we know exactly which <code>paste()</code> call is the culprit.</p>
<p>Each function in {debrief} also prints the name of the possible next functions to run,
so the assistant can drill down into the details.</p>
<p>debrief is a new package.
Its output almost certainly isn&rsquo;t part of any model&rsquo;s training data.
And output is also very unlikely to be added,
as profiling code is rarely committed.
This means that the package tries to be self-documenting by design,
With the hope that an AI agent doesn&rsquo;t need to read the documentation to understand how to use it.</p>
<h2 id="getting-an-agent-into-the-session">Getting an agent into the session
</h2>
<p>While you can use debrief with any AI agent,
it works better with agents that keep a persistent R session.
Otherwise, it will need to rerun the profile every time it wants to check the results of an edit.
You also won&rsquo;t be able to take full advantage of the &ldquo;Next steps&rdquo; hints,
which are designed to guide the agent through drilling down into the profile.</p>
<p><a href="https://posit.com/assistant" target="_blank" rel="noopener">Posit Assistant</a> is the easiest way to do this.
It runs in the same session as you, so you can be a bigger part of the process.
The same assistant runs in Positron, RStudio, or the terminal,
so you can profile interactively or drive the whole loop headlessly from the <code>pa</code> CLI.</p>
<p>If you aren&rsquo;t using RStudio or Positron, or prefer an agent like Claude Code or Codex,
<a href="https://github.com/posit-dev/mcp-repl" target="_blank" rel="noopener">mcp-repl</a> gives you the same thing.
It&rsquo;s an MCP server that hands any MCP-capable agent a persistent R (or Python) session,
so you can load debrief and run the profiler once, then drill down as needed.</p>
<h2 id="trying-it-out">Trying it out
</h2>
<p>The point of debrief is to put profiling results in a form an agent can easily interact with,
so it can measure instead of guess and iterate toward faster code.
We have already used it this way:
<a href="https://github.com/tidymodels/textrecipes/pull/309" target="_blank" rel="noopener">tidymodels/textrecipes#309</a>
is a profiling-driven optimization of <code>step_word_embeddings()</code>,
where each round of profile, read, change, and re-measure was guided by debrief&rsquo;s summaries.</p>
<p>Install debrief, point an agent at a slow function, and let it profile.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-22_debrief-0-1-0/featured.png" length="332953" type="image/png" />
    </item>
    <item>
      <title>pkgsite 0.1.0: Convert your `.Rd` files to Quarto</title>
      <link>https://opensource.posit.co/blog/2026-06-18_pkgsite-0-1-0/</link>
      <pubDate>Thu, 18 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-18_pkgsite-0-1-0/</guid>
      <dc:creator>Edgar Ruiz</dc:creator><description><![CDATA[<p>We are happy to introduce <code>pkgsite</code>. It reads the compiled <code>.Rd</code> files in your
R package and converts them into <code>.qmd</code> files. It can also automatically create
a reference index page that lists all your exported functions. You can customize
the format of both using templates, then let Quarto render the HTML. <code>pkgsite</code>
is now available on CRAN, to install use:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;pkgsite&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p><code>pkgsite</code> is inspired by Python&rsquo;s <a href="https://machow.github.io/quartodoc/get-started/overview.html" target="_blank" rel="noopener"><code>Quartodoc</code></a>,
which does the same for Python packages.</p>
<h2 id="why-pkgsite">Why <code>pkgsite</code>?
</h2>
<p>The main website builder for R packages is <a href="https://pkgdown.r-lib.org/" target="_blank" rel="noopener"><code>pkgdown</code></a>.
It goes all the way to a finished, publication-ready HTML website in one step,
and for most packages that is exactly what you want. It is mature, has a large
ecosystem of themes and extensions, and just works.</p>
<p><code>pkgsite</code>, on the other hand, only creates <code>.qmd</code> files with the help content.
You decide how to structure the site around them, and Quarto handles the final
HTML output.</p>
<p>To see what that looks like, compare the
<a href="https://github.com/mlverse/mall/blob/main/r/man/llm_sentiment.Rd" target="_blank" rel="noopener">source <code>.Rd</code> file</a>
for <code>llm_sentiment()</code> in <code>mall</code> with the
<a href="https://github.com/mlverse/mall/blob/main/reference/llm_sentiment.qmd" target="_blank" rel="noopener"><code>.qmd</code> file <code>pkgsite</code> generated from it</a>.</p>
<p>Two cases where we have seen it make a difference are:</p>
<ul>
<li>Examples that require local resources</li>
<li>Unified R and Python Quarto sites</li>
</ul>
<h3 id="examples-that-require-local-resources">Examples that require local resources
</h3>
<p>Some packages depend on things that are not available on automated build and
publishing platforms such as GitHub Actions or Netlify: databases, large
language models, or Spark clusters. Running their examples there is not
feasible, yet you still want working, rendered documentation.</p>
<p>Quarto&rsquo;s <a href="https://quarto.org/docs/projects/code-execution.html#freeze" target="_blank" rel="noopener">freeze</a>
solves this cleanly. You render the site once locally where those resources are
available, commit the <code>_freeze/</code> folder alongside your source, and GitHub
rebuilds the site on every push without re-executing a single line of code.</p>
<p>The <a href="https://mlverse.github.io/mall/" target="_blank" rel="noopener"><code>mall</code></a> and
<a href="https://mlverse.github.io/lang/" target="_blank" rel="noopener"><code>lang</code></a> packages are concrete examples.
Their function examples call an LLM, so they cannot run on those platforms.
With <code>pkgsite</code> and freeze, rendering happens on a developer machine where the
model is accessible, and the frozen output travels with the repository.</p>
<p>In the <code>llm_sentiment()</code> function, the example section looks like this in the
resulting <code>.qmd</code> file:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="gu">## Examples
</span></span></span><span class="line"><span class="cl">``<span class="sb">`{r}
</span></span></span><span class="line"><span class="cl"><span class="sb">library(mall)
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">data(&#34;reviews&#34;)
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">llm_use(&#34;ollama&#34;, &#34;llama3.2&#34;, seed = 100, .silent = TRUE)
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">llm_sentiment(reviews, review)
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb"># Use &#39;pred_name&#39; to customize the new column&#39;s name
</span></span></span><span class="line"><span class="cl"><span class="sb">llm_sentiment(reviews, review, pred_name = &#34;review_sentiment&#34;)
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span>``</span></span></code></pre></div></div>
<p>And this is what the <a href="https://mlverse.github.io/mall/reference/llm_sentiment.html#examples" target="_blank" rel="noopener">rendered page</a> looks like on the <code>mall</code> website:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-18_pkgsite-0-1-0/llm-sentiment-page.png"
      alt="The rendered llm_sentiment() reference page on the mall website, showing the function description and executed example output."  title="Examples rendered locally using Quarto freeze" 
      loading="lazy"
    ><figcaption class="text-sm text-center text-gray-500">Examples rendered locally using Quarto freeze</figcaption>
  </figure></div>
</p>
<h3 id="unified-r-and-python-sites">Unified R and Python sites
</h3>
<p>If your project ships both an R package and a Python package, you can combine
<code>pkgsite</code>&rsquo;s output with
<a href="https://machow.github.io/quartodoc/get-started/overview.html" target="_blank" rel="noopener"><code>Quartodoc</code></a>&rsquo;s
output into a single Quarto website. Both tools write <code>.qmd</code> reference pages
that Quarto assembles together, giving R and Python users a consistent
experience on one site. The <code>mall</code> package&rsquo;s
<a href="https://mlverse.github.io/mall/reference/" target="_blank" rel="noopener">reference section</a> is a live
example: R and Python pages side by side, built from two different tools,
served as one site.</p>
<h2 id="getting-started">Getting started
</h2>
<p>From within your package directory, one function call does the work:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">pkgsite</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">write_reference</span><span class="p">()</span></span></span></code></pre></div></div>
<p><code>write_reference()</code> creates a <code>reference/index.qmd</code> that links to all your
exported functions, and converts each <code>.Rd</code> file in <code>man/</code> into its own <code>.qmd</code>
reference page.</p>
<p>You can customize its behavior through arguments. For example, if you want to
skip running the examples when Quarto renders the reference pages:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">write_reference</span><span class="p">(</span><span class="n">examples</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">,</span> <span class="n">not_run_examples</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Calling it without arguments reads any configuration from a <code>pkgsite:</code> section
at the top level of <code>_quarto.yml</code>. Following the same convention as
<a href="https://machow.github.io/quartodoc/get-started/overview.html" target="_blank" rel="noopener"><code>Quartodoc</code></a>,
the Python equivalent, this is where you set the package root, the output
folder, templates, and optionally how functions are grouped and ordered in the
index:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">pkgsite</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">dir</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;.&#34;</span><span class="w">                    </span><span class="c"># path to the package root</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">reference</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">dir</span><span class="p">:</span><span class="w"> </span><span class="l">reference           </span><span class="w"> </span><span class="c"># where to write the .qmd files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">not_run_examples</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">   </span><span class="c"># whether to execute \dontrun{} examples</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">template</span><span class="p">:</span><span class="w"> </span><span class="l">inst/templates/_reference.qmd  </span><span class="w"> </span><span class="c"># custom page template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">index</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">file</span><span class="p">:</span><span class="w"> </span><span class="l">index.qmd        </span><span class="w"> </span><span class="c"># name of the index file</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">template</span><span class="p">:</span><span class="w"> </span><span class="l">inst/templates/_index.qmd    </span><span class="w"> </span><span class="c"># custom index template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">contents:               # optional</span><span class="p">:</span><span class="w"> </span><span class="l">custom function grouping</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">section</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Write files&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">contents</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">write_reference.qmd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">write_reference_index.qmd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">write_reference_pages.qmd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">section</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Conversion&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">contents</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">rd_to_qmd.qmd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">rd_to_list.qmd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">index_to_qmd.qmd</span></span></span></code></pre></div></div>
<p>If you omit <code>contents</code>, <code>pkgsite</code> falls back to grouping by <code>roxygen2</code>
<code>@family</code> tags, then alphabetical order. You only need to re-run
<code>write_reference()</code> when you add, rename, or remove exported functions.</p>
<p>Use arguments for one-off adjustments; the <code>_quarto.yml</code> configuration is the
better choice when you want those settings to apply consistently every time
<code>write_reference()</code> is called.</p>
<p>This is what the rendered reference index page looks like on the <code>pkgsite</code>
website, using the grouping specified in the example YAML above:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-18_pkgsite-0-1-0/pkgsite-reference-index.png"
      alt="The pkgsite reference index page, with exported functions organized into named sections like &ldquo;Write files&rdquo; and &ldquo;Conversion&rdquo;."  title="Group functions into sections in the YAML file" 
      loading="lazy"
    ><figcaption class="text-sm text-center text-gray-500">Group functions into sections in the YAML file</figcaption>
  </figure></div>
</p>
<h2 id="customizing-the-page-layout">Customizing the page layout
</h2>
<p>The layout of every reference page and the index is driven by a Quarto template
file that uses four-curly-brace placeholders like <code>{{{{title.description}}}}</code>.
The prefix — <code>title.</code> or <code>notitle.</code> — controls whether the section heading is
included. The defaults work well for most packages, but if you want to
re-order sections, add a logo, link to source code, or adjust per-page
frontmatter, you can supply your own template. For example, a minimal template
that shows only the title, description, and examples looks like this:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">title: &#34;{{{{notitle.title}}}}&#34;
</span></span><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## {{{{notitle.title}}}}
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Description
</span></span></span><span class="line"><span class="cl">{{{{title.description}}}}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Examples
</span></span></span><span class="line"><span class="cl">{{{{title.examples}}}}</span></span></code></pre></div></div>
<p>The
<a href="https://edgararuiz.github.io/pkgsite/articles/customize.html" target="_blank" rel="noopener">Customize the pages</a>
article covers the full set of available placeholders.</p>
<p>The <code>mall</code> package is a good example of this. Its custom template makes two
additions to the default: it adjusts how <code>knitr</code> renders table column widths,
and it adds a link to the R source code of each function on GitHub:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-18_pkgsite-0-1-0/mall-custom-template.png"
      alt="A mall reference page built from a custom template, with a &ldquo;Source code&rdquo; link to the function&rsquo;s R source file on GitHub."  title="Add custom elements to reference pages with a template" 
      loading="lazy"
    ><figcaption class="text-sm text-center text-gray-500">Add custom elements to reference pages with a template</figcaption>
  </figure></div>
</p>
<h2 id="publishing-to-github-pages">Publishing to GitHub Pages
</h2>
<p>To publish your site on every push to <code>main</code>, you need a GitHub Actions
workflow that runs <code>quarto render</code> and deploys the output to the <code>gh-pages</code>
branch. The
<a href="https://edgararuiz.github.io/pkgsite/articles/github-actions.html" target="_blank" rel="noopener">GitHub Pages</a>
article on the <code>pkgsite</code> website walks through the full setup with a working
example.</p>
<h3 id="auto-linking-function-names">Auto-linking function names
</h3>
<p><code>pkgsite</code> can turn every function name in your prose into a link pointing to
its own reference page automatically. Mention <code>llm_sentiment()</code> anywhere in
your documentation and it becomes a clickable link to the <code>llm_sentiment</code>
reference page, no extra markup needed. The same
<a href="https://edgararuiz.github.io/pkgsite/articles/github-actions.html" target="_blank" rel="noopener">GitHub Pages</a>
article covers how to enable it.</p>
<h2 id="learn-more">Learn more
</h2>
<p>The full documentation lives at
<a href="https://edgararuiz.github.io/pkgsite/" target="_blank" rel="noopener">edgararuiz.github.io/pkgsite</a>, and the
source is on <a href="https://github.com/edgararuiz/pkgsite" target="_blank" rel="noopener">GitHub</a>. Issues and
feature requests go to the
<a href="https://github.com/edgararuiz/pkgsite/issues" target="_blank" rel="noopener">issue tracker</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-18_pkgsite-0-1-0/logo.png" length="1076855" type="image/png" />
    </item>
    <item>
      <title>webR 0.6.0</title>
      <link>https://opensource.posit.co/blog/2026-06-18_webr-0-6-0/</link>
      <pubDate>Thu, 18 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-18_webr-0-6-0/</guid>
      <dc:creator>George Stagg</dc:creator><description><![CDATA[<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css">
<style>
  .webr-run-button {
    background-color: #F7F7F7;
    border: 1px solid #EEE;
    padding: 0.1em 0.5em;
    border-top-left-radius: 5px;
    border-top-right-radius: 5px;
    cursor: pointer;
    font-size: 0.9em;
  }
  .webr-run-button:hover {
    background-color: #EEE;
  }
  .webr-run-button:active {
    background-color: #DDD;
  }
  .webr-run-button:disabled {
    background-color: #DDD;
    cursor: not-allowed;
    transform: none;
  }
  .webr-code-output pre {
    margin-top: 0;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
  }
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/mode/r/r.js"></script>
<script type="module">
  import { WebR } from 'https://webr.r-wasm.org/latest/webr.mjs';
  globalThis.webRIndex = 0;
  globalThis.webREditorCode = [];
  globalThis.webR = new WebR();
  globalThis.webRPlotWidth = 640;
  globalThis.webRPlotHeight = 400;
  await globalThis.webR.init();
  await webR.evalRVoid("webr::shim_install()");
  await webR.evalRVoid("webr::global_prompt_install()", { withHandlers: false });
  globalThis.webRCodeShelter = await new globalThis.webR.Shelter();
  document.querySelectorAll(".webr-run-button").forEach((btn) => {
    btn.innerText = 'Run code';
    btn.disabled = false;
  });
</script>
<p>We&rsquo;re delighted to announce the release of webR 0.6.0. WebR brings R to the web browser using WebAssembly, powering <a href="https://r-wasm.github.io/quarto-live/" target="_blank" rel="noopener">Quarto Live</a>, <a href="https://shinylive.io/r/examples/" target="_blank" rel="noopener">Shinylive</a>, and other interactive R experiences.</p>
<p>As a reminder of what webR can do, here is an interactive code editor running R in the browser. Try it out!</p>
<script type="module">
 globalThis.webREditorCode.push(`model <- lm(body_mass ~ bill_len, penguins)
summary(model)

boxplot(
  penguins$body_mass ~ penguins$species,
  xlab = 'Species',
  ylab = 'Body Mass (g)',
  col = c('#06a', '#f80', '#085')
)`)
</script>
<div><button class="webr-run-button" disabled type="button">Loading webR...</button></div>
<div class="webr-editor"></div>
<div class="webr-code-output"><pre style="visibility: hidden"></pre></div>
<script type="module">
  const runButton = document.getElementsByClassName('webr-run-button')[globalThis.webRIndex];
  const editorDiv = document.getElementsByClassName('webr-editor')[globalThis.webRIndex];
  const outputDiv = document.getElementsByClassName('webr-code-output')[globalThis.webRIndex];
  const codeValue = globalThis.webREditorCode[globalThis.webRIndex];
  globalThis.webRIndex++;
  const editor = CodeMirror((elt) => {
    elt.style.border = '1px solid #eee';
    elt.style.height = 'auto';
    editorDiv.append(elt);
  },{
    value: codeValue,
    lineNumbers: true,
    mode: 'r',
    theme: 'light default',
    viewportMargin: Infinity,
  });
  runButton.onclick = async () => {
    runButton.disabled = true;
    let canvas = undefined;
    await webR.init();
    await webR.evalRVoid(`webr::canvas(width = ${globalThis.webRPlotWidth}, height = ${globalThis.webRPlotHeight})`);
    const result = await webRCodeShelter.captureR(editor.getValue(), {
      withAutoprint: true,
      captureStreams: true,
      captureConditions: false,
      captureGraphics: false,
      env: {},
    });
    try {
      await webR.evalRVoid("dev.off()");
      const out = result.output.filter(
        evt => evt.type == 'stdout' || evt.type == 'stderr'
      ).map((evt) => evt.data).join('\n');
      outputDiv.innerHTML = '';
      const pre = document.createElement("pre");
      if (/\S/.test(out)) {
        const code = document.createElement("code");
        code.innerText = out;
        pre.appendChild(code);
      } else {
        pre.style.visibility = 'hidden';
      }
      outputDiv.appendChild(pre);
      const msgs = await webR.flush();
      msgs.forEach(msg => {
        if (msg.type === 'canvas'){
          if (msg.data.event === 'canvasImage') {
            canvas.getContext('2d').drawImage(msg.data.image, 0, 0);
          } else if (msg.data.event === 'canvasNewPage') {
            canvas = document.createElement('canvas');
            canvas.setAttribute('width', 2 * globalThis.webRPlotWidth);
            canvas.setAttribute('height', 2 * globalThis.webRPlotHeight);
            canvas.style.width="640px";
            canvas.style.display="block";
            canvas.style.margin="auto";
            const p = document.createElement("p");
            p.appendChild(canvas);
            outputDiv.appendChild(p);
          }
        }
      });
    } finally {
      webRCodeShelter.purge();
      runButton.disabled = false;
    }
  }
</script>
<p>It&rsquo;s been almost a year since I&rsquo;ve written a blog post about webR, and so this post highlights some key features and improvements over the last few releases. For a full list of changes, see the recent <a href="https://github.com/r-wasm/webr/releases" target="_blank" rel="noopener">release notes on GitHub</a>.</p>
<h2 id="updates-to-r-emscripten-and-llvm">Updates to R, Emscripten, and LLVM
</h2>
<p>We&rsquo;ve updated the version of R on which webR is based to version 4.6.0, ensuring we have all the latest improvements and bug fixes from the R core team. For example, the new <code>%notin%</code> operator can now be used:</p>
<script type="module">
 globalThis.webREditorCode.push("sessionInfo()[[1]]$version.string\n4 %notin% 1:10\n4 %notin% c(1, 2, 3)")
</script>
<div><button class="webr-run-button" disabled type="button">Loading webR...</button></div>
<div class="webr-editor"></div>
<div class="webr-code-output"><pre style="visibility: hidden"></pre></div>
<script type="module">
  const runButton = document.getElementsByClassName('webr-run-button')[globalThis.webRIndex];
  const editorDiv = document.getElementsByClassName('webr-editor')[globalThis.webRIndex];
  const outputDiv = document.getElementsByClassName('webr-code-output')[globalThis.webRIndex];
  const codeValue = globalThis.webREditorCode[globalThis.webRIndex];
  globalThis.webRIndex++;
  const editor = CodeMirror((elt) => {
    elt.style.border = '1px solid #eee';
    elt.style.height = 'auto';
    editorDiv.append(elt);
  },{
    value: codeValue,
    lineNumbers: true,
    mode: 'r',
    theme: 'light default',
    viewportMargin: Infinity,
  });
  runButton.onclick = async () => {
    runButton.disabled = true;
    let canvas = undefined;
    await webR.init();
    await webR.evalRVoid(`webr::canvas(width = ${globalThis.webRPlotWidth}, height = ${globalThis.webRPlotHeight})`);
    const result = await webRCodeShelter.captureR(editor.getValue(), {
      withAutoprint: true,
      captureStreams: true,
      captureConditions: false,
      captureGraphics: false,
      env: {},
    });
    try {
      await webR.evalRVoid("dev.off()");
      const out = result.output.filter(
        evt => evt.type == 'stdout' || evt.type == 'stderr'
      ).map((evt) => evt.data).join('\n');
      outputDiv.innerHTML = '';
      const pre = document.createElement("pre");
      if (/\S/.test(out)) {
        const code = document.createElement("code");
        code.innerText = out;
        pre.appendChild(code);
      } else {
        pre.style.visibility = 'hidden';
      }
      outputDiv.appendChild(pre);
      const msgs = await webR.flush();
      msgs.forEach(msg => {
        if (msg.type === 'canvas'){
          if (msg.data.event === 'canvasImage') {
            canvas.getContext('2d').drawImage(msg.data.image, 0, 0);
          } else if (msg.data.event === 'canvasNewPage') {
            canvas = document.createElement('canvas');
            canvas.setAttribute('width', 2 * globalThis.webRPlotWidth);
            canvas.setAttribute('height', 2 * globalThis.webRPlotHeight);
            canvas.style.width="640px";
            canvas.style.display="block";
            canvas.style.margin="auto";
            const p = document.createElement("p");
            p.appendChild(canvas);
            outputDiv.appendChild(p);
          }
        }
      });
    } finally {
      webRCodeShelter.purge();
      runButton.disabled = false;
    }
  }
</script>
<p>Under the hood, we&rsquo;ve also upgraded <a href="https://emscripten.org" target="_blank" rel="noopener">Emscripten</a> to version 5.0.7. Emscripten serves as the crucial layer between the web browser and R&rsquo;s source code, playing a role similar to an operating system. We&rsquo;ve also updated the version of LLVM we use to compile Fortran sources to 21.1.8.</p>
<h3 id="improvements-to-fortran-on-webassembly">Improvements to Fortran on WebAssembly
</h3>
<p>The R source code contains a surprising amount of Fortran code (the bulk of which written in F77-style), even today. For webR, we rely on a forked version of LLVM Flang with custom patches to add support for emitting WebAssembly. I won&rsquo;t go into the deep technical details here, but I have <a href="https://gws.phd/posts/fortran_wasm/" target="_blank" rel="noopener">written about it before</a> if you are interested.</p>
<p>For a long time, dynamic array operations introduced in more modern Fortran standards were not supported by our fork. This meant that some packages which use these Fortran features would not work in webR, crashing with a dense and largely unhelpful runtime error message:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fortran" data-lang="fortran"><span class="line"><span class="cl"><span class="k">program</span><span class="w"> </span><span class="n">p</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">integer</span><span class="w"> </span><span class="kd">::</span><span class="w"> </span><span class="n">a</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">print</span><span class="w"> </span><span class="o">*</span><span class="p">,</span><span class="w"> </span><span class="nb">sum</span><span class="p">(</span><span class="n">a</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">end</span><span class="w"> </span><span class="k">program</span></span></span></code></pre></div></div>
<pre><code>$ ./previous/flang -c arr.f90 -o arr.o
$ emcc arr.o libFortranRuntime.a -o arr.js
$ node arr.js
  fatal Fortran runtime error(...reduction-templates.h(90)): Internal error:
  RUNTIME_CHECK(TypeCode(CAT, KIND) == x.type() || ...) failed
</code></pre>
<p>However, thanks to a <a href="https://github.com/r-wasm/flang-wasm/issues/9" target="_blank" rel="noopener">hint originating from a community member</a>, we&rsquo;ve now added a workaround for the underlying compiler problem, and R packages which rely on these Fortran features should now work in webR without crashing:</p>
<pre><code>$ ./latest/flang -c arr.f90 -o arr.o
$ emcc arr.o libFortranRuntime.a -o arr.js
$ node arr.js
  15
</code></pre>
<h2 id="additional-system-libraries">Additional system libraries
</h2>
<p>We&rsquo;ve also added or updated the following system libraries compiled to WebAssembly, which are now available for use by R packages in webR:</p>
<ul>
<li>cacert.pem 2026-05-14</li>
<li>fontconfig v2.15.0</li>
<li>libpoppler v24.12.0</li>
<li>libsodium v1.0.22</li>
<li>libtiff v4.7.1</li>
<li>libuv v1.44.2</li>
<li>openssl v3.5.1</li>
</ul>
<h2 id="asyncawait-in-webreval_js">Async/await in <code>webr::eval_js()</code>
</h2>
<p>One of the biggest issues when working with webR&rsquo;s JavaScript API is that we must block the worker thread running the WebAssembly R process. This is required to make sure we can wait and take input from the user, both at the top-level and in functions like <code>readline()</code> or <code>browser()</code>. Previously, this meant that only synchronous JavaScript code could be run from R via <code>webr::eval_js()</code>, and JavaScript features like <code>Promise</code>, <code>async</code> or <code>await</code> were not available.</p>
<p>By proxying the JavaScript code to the main thread, we&rsquo;ve added support for running asynchronous JavaScript code in <code>webr::eval_js()</code>. You can now use <code>await = TRUE</code> to wait for a <code>Promise</code> to resolve before returning a value to R:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">webr</span><span class="o">::</span><span class="nf">eval_js</span><span class="p">(</span><span class="s">&#34;new Promise((res) =&gt; res(1729))&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code>[1] 1729
</code></pre>
<p>Or equivalently, you can use <code>await</code> inside an <code>async</code> JavaScript function:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">webr</span><span class="o">::</span><span class="nf">eval_js</span><span class="p">(</span><span class="n">r</span><span class="s">&#34;{
</span></span></span><span class="line"><span class="cl"><span class="s">  (async () =&gt; {
</span></span></span><span class="line"><span class="cl"><span class="s">    await new Promise(r =&gt; setTimeout(r, 500));
</span></span></span><span class="line"><span class="cl"><span class="s">    return 87539319;
</span></span></span><span class="line"><span class="cl"><span class="s">  })();
</span></span></span><span class="line"><span class="cl"><span class="s">}&#34;</span><span class="p">,</span> <span class="n">await</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code>[1] 87539319
</code></pre>
<h2 id="using-curl-under-webassembly">Using curl under WebAssembly
</h2>
<p>Using similar techniques, we have also added support for proxying WebSocket traffic via the main thread. This, combined with some <a href="https://jeroen.github.io/notes/webassembly-curl/" target="_blank" rel="noopener">other work for libcurl in WebAssembly</a>, means we can now use the curl and httr2 R packages in webR.</p>
<script type="module">
 globalThis.webREditorCode.push(`# Load the R package
library(httr2)

# Create a request
req <- request("https://r-project.org")
req`)
</script>
<div><button class="webr-run-button" disabled type="button">Loading webR...</button></div>
<div class="webr-editor"></div>
<div class="webr-code-output"><pre style="visibility: hidden"></pre></div>
<script type="module">
  const runButton = document.getElementsByClassName('webr-run-button')[globalThis.webRIndex];
  const editorDiv = document.getElementsByClassName('webr-editor')[globalThis.webRIndex];
  const outputDiv = document.getElementsByClassName('webr-code-output')[globalThis.webRIndex];
  const codeValue = globalThis.webREditorCode[globalThis.webRIndex];
  globalThis.webRIndex++;
  const editor = CodeMirror((elt) => {
    elt.style.border = '1px solid #eee';
    elt.style.height = 'auto';
    editorDiv.append(elt);
  },{
    value: codeValue,
    lineNumbers: true,
    mode: 'r',
    theme: 'light default',
    viewportMargin: Infinity,
  });
  runButton.onclick = async () => {
    runButton.disabled = true;
    let canvas = undefined;
    await webR.init();
    await webR.evalRVoid(`webr::canvas(width = ${globalThis.webRPlotWidth}, height = ${globalThis.webRPlotHeight})`);
    const result = await webRCodeShelter.captureR(editor.getValue(), {
      withAutoprint: true,
      captureStreams: true,
      captureConditions: false,
      captureGraphics: false,
      env: {},
    });
    try {
      await webR.evalRVoid("dev.off()");
      const out = result.output.filter(
        evt => evt.type == 'stdout' || evt.type == 'stderr'
      ).map((evt) => evt.data).join('\n');
      outputDiv.innerHTML = '';
      const pre = document.createElement("pre");
      if (/\S/.test(out)) {
        const code = document.createElement("code");
        code.innerText = out;
        pre.appendChild(code);
      } else {
        pre.style.visibility = 'hidden';
      }
      outputDiv.appendChild(pre);
      const msgs = await webR.flush();
      msgs.forEach(msg => {
        if (msg.type === 'canvas'){
          if (msg.data.event === 'canvasImage') {
            canvas.getContext('2d').drawImage(msg.data.image, 0, 0);
          } else if (msg.data.event === 'canvasNewPage') {
            canvas = document.createElement('canvas');
            canvas.setAttribute('width', 2 * globalThis.webRPlotWidth);
            canvas.setAttribute('height', 2 * globalThis.webRPlotHeight);
            canvas.style.width="640px";
            canvas.style.display="block";
            canvas.style.margin="auto";
            const p = document.createElement("p");
            p.appendChild(canvas);
            outputDiv.appendChild(p);
          }
        }
      });
    } finally {
      webRCodeShelter.purge();
      runButton.disabled = false;
    }
  }
</script>
<p>Try out a <a href="https://webr.r-wasm.org/latest/#code=eJxdjzFuwkAQRfs9xQoaWwpeQYmiNGlTREipV4M9sU3s3c3MWAiJjhNwBzpukDr3IstipIhq%2Fteb%2FzVzPJwc9Hj%2BcNJKh9W8WJ0CSPNjGt%2Bj2eLaDoxk%2FvMKBH4vU%2F3modLSoF7pAOUX1KhaxwJdV4yes0kjQotJrrp2TUC7LPlcqal%2BJQRBDZrwe0AWFad%2Bnt1tigZeGkOzQH6DpRSe6lgVF675d6RPT326YMw8RSEDudbVqZeDd4zqKsZmG26pLOr8BvYvadOW3gk6sbILmD2w%2BJUMbCvkckR%2Fegdw2w%3D%3D" target="_blank" rel="noopener">full example R script using httr2 in the webR app</a>.</p>
<h2 id="dark-mode-for-the-webr-app">Dark mode for the webR app
</h2>
<p>Speaking of the webR app, if your system is set to dark mode the webR app will now automatically switch to a dark theme. This feature was contributed by an attendee of <a href="https://tidyverse.org/tags/tidyverse-dev-day/" target="_blank" rel="noopener">Tidyverse Developer Day 2025</a>. Thanks, Kyle!</p>
<figure>
<img src="https://opensource.posit.co/blog/2026-06-18_webr-0-6-0/dark.png" data-fig-alt="A screenshot of the webR app. On the left shows the app with light mode theme. On the right shows the app in dark mode theme." alt="The webR app in light and dark." />
<figcaption aria-hidden="true">The webR app in light and dark.</figcaption>
</figure>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>And, as always, a huge thank you to everyone else who has contributed to webR, either by submitting bug reports, contributing code, or just sharing feedback.</p>
<p><a href="https://github.com/AABoyles" target="_blank" rel="noopener">@AABoyles</a>, <a href="https://github.com/ai-petri" target="_blank" rel="noopener">@ai-petri</a>, <a href="https://github.com/brendanhcullen" target="_blank" rel="noopener">@brendanhcullen</a>, <a href="https://github.com/bryce-carson" target="_blank" rel="noopener">@bryce-carson</a>, <a href="https://github.com/chizapoth" target="_blank" rel="noopener">@chizapoth</a>, <a href="https://github.com/coatless" target="_blank" rel="noopener">@coatless</a>, <a href="https://github.com/daniel-woodie" target="_blank" rel="noopener">@daniel-woodie</a>, <a href="https://github.com/dipterix" target="_blank" rel="noopener">@dipterix</a>, <a href="https://github.com/dusadrian" target="_blank" rel="noopener">@dusadrian</a>, <a href="https://github.com/georgestagg" target="_blank" rel="noopener">@georgestagg</a>, <a href="https://github.com/goebbe" target="_blank" rel="noopener">@goebbe</a>, <a href="https://github.com/HenrikBengtsson" target="_blank" rel="noopener">@HenrikBengtsson</a>, <a href="https://github.com/jeroen" target="_blank" rel="noopener">@jeroen</a>, <a href="https://github.com/jgf5013" target="_blank" rel="noopener">@jgf5013</a>, <a href="https://github.com/jmbo1190" target="_blank" rel="noopener">@jmbo1190</a>, <a href="https://github.com/JosiahParry" target="_blank" rel="noopener">@JosiahParry</a>, <a href="https://github.com/khusmann" target="_blank" rel="noopener">@khusmann</a>, <a href="https://github.com/kyleGrealis" target="_blank" rel="noopener">@kyleGrealis</a>, <a href="https://github.com/laderast" target="_blank" rel="noopener">@laderast</a>, <a href="https://github.com/lpmi-13" target="_blank" rel="noopener">@lpmi-13</a>, <a href="https://github.com/pepijn-devries" target="_blank" rel="noopener">@pepijn-devries</a>, <a href="https://github.com/seanbirchall" target="_blank" rel="noopener">@seanbirchall</a>, <a href="https://github.com/szcf-weiya" target="_blank" rel="noopener">@szcf-weiya</a>, <a href="https://github.com/timelyportfolio" target="_blank" rel="noopener">@timelyportfolio</a>, <a href="https://github.com/tulaydixon" target="_blank" rel="noopener">@tulaydixon</a>, <a href="https://github.com/WardBrian" target="_blank" rel="noopener">@WardBrian</a>, and <a href="https://github.com/WillemSleegers" target="_blank" rel="noopener">@WillemSleegers</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-18_webr-0-6-0/bricks.jpg" length="612027" type="image/jpeg" />
    </item>
    <item>
      <title>Ask more of your dashboard with querychat and ggsql</title>
      <link>https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/</link>
      <pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/</guid>
      <dc:creator>Carson Sievert</dc:creator><description><![CDATA[<p>I&rsquo;m super excited to announce that <a href="https://posit-dev.github.io/querychat/" target="_blank" rel="noopener">querychat</a> now supports <a href="https://opensource.posit.co/blog/2026-04-20_ggsql_alpha_release">ggsql</a>. As a result, querychat users can now go beyond predetermined dashboard views and ask the LLM for bespoke visualizations on the fly. With this addition, querychat now comes with three pre-built tools that LLMs can use in response to user questions:</p>
<ol>
<li><strong>Visualize:</strong> execute <code>ggsql</code> queries, rendered as visualizations in the chat.</li>
<li><strong>Query:</strong> execute SQL queries, rendered as tables/text in the chat.</li>
<li><strong>Filter:</strong> execute filter SQL queries on predetermined views, allowing the LLM to reactively drill into the data in response to user questions.</li>
</ol>
<p>Equipped with these tools, your dashboard can offer a balance between &ldquo;curated insights&rdquo; and &ldquo;explorable data&rdquo; &mdash; surfacing interesting trends and summaries upfront, but also letting users ask follow-up questions that go beyond the predetermined views. The video below gives you a feel for this workflow in action<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. Note how the conversation starts off by filtering the predetermined views, but then progresses into more general exploration.</p>
<script src="https://fast.wistia.com/player.js" async></script>
<script src="https://fast.wistia.com/embed/at01coepkh.js" async type="module"></script>
<style>wistia-player[media-id='at01coepkh']:not(:defined) { background: center / contain no-repeat url('https://fast.wistia.com/embed/medias/at01coepkh/swatch'); display: block; filter: blur(5px); padding-top:58.76%; }</style>
<p><wistia-player media-id="at01coepkh" aspect="1.701851851851852"></wistia-player></p>
<p>Crucially, every one of <code>querychat</code>&rsquo;s tools works by executing SQL (or <code>ggsql</code>) &mdash; never arbitrary code. Every query is also fully visible and reproducible: users can inspect the code behind any response, and copy it to run independently. In the next version of <code>querychat</code>, you can also expect to see a rich export capability, allowing you to save the insights you care about in a format you care about (e.g. a Quarto dashboard, Jupyter notebook, or R Markdown file).</p>
<p>Restricting <code>querychat</code>&rsquo;s code execution capability to SQL (or <code>ggsql</code>) is an intentional design choice. We can guarantee that querychat does not run arbitrary R or python code, making it viable for production environments where security and control are required. As a result, <code>querychat</code> may not be as capable as a general coding assistant like <a href="https://posit-dev.github.io/assistant" target="_blank" rel="noopener">Posit Assistant</a>, but you generally don&rsquo;t want production apps that let users execute arbitrary code. By focusing on SQL execution, <code>querychat</code> can maintain a strong security posture while still enabling useful exploration of data via natural language.</p>
<h2 id="get-started">Get started
</h2>
<p>Getting started is as easy as pointing <code>QueryChat()</code> at your data source and getting set up with an LLM. Although we recommend frontier models for an ideal experience, open-weight models have recently become <a href="https://simonpcouch.com/blog/2026-04-16-local-agents-2/" target="_blank" rel="noopener">quite capable</a>, and can be a great way to get started without needing API access or incurring costs.</p>
<p>For this article, I&rsquo;m using <a href="https://lmstudio.ai/" target="_blank" rel="noopener">LMStudio</a> to run <code>google/gemma-4-26b-a4b</code> locally on my fairly standard MacBook Pro. If you prefer a different LLM or provider, refer to the <a href="https://posit-dev.github.io/querychat/py/models.html" target="_blank" rel="noopener">docs</a> (<a href="https://posit-dev.github.io/querychat/r/articles/models.html" target="_blank" rel="noopener">R</a>) to see how to configure.</p>
<p>Once you&rsquo;ve picked an LLM, next step is ensuring both <code>querychat</code> and <code>ggsql</code> are installed. Here we&rsquo;ll also use the <code>palmerpenguins</code> data set to keep examples lightweight and self-contained, but you can point <code>querychat</code> at anything from a data frame to a <a href="https://posit-dev.github.io/querychat/py/data-sources.html" target="_blank" rel="noopener">remote database connection</a> (<a href="https://posit-dev.github.io/querychat/r/articles/data-sources.html" target="_blank" rel="noopener">R</a>).</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-1" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-1-1">Python</a></li>
<li><a href="#tabset-1-2">R</a></li>
</ul>
<div id="tabset-1-1">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install <span class="s2">&#34;querychat[viz]&#34;</span> palmerpenguins</span></span></code></pre></div></div>
</div>
<div id="tabset-1-2">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="nf">c</span><span class="p">(</span><span class="s">&#34;querychat&#34;</span><span class="p">,</span> <span class="s">&#34;ggsql&#34;</span><span class="p">,</span> <span class="s">&#34;palmerpenguins&#34;</span><span class="p">))</span></span></span></code></pre></div></div>
</div>
</div>
<h2 id="basic-usage">Basic usage
</h2>
<p>Once installed, you can get a basic chat UI using the code below. We&rsquo;ll include just the <code>query</code> and <code>visualize</code> tools here since the <code>filter</code> tool is designed to work in the context of a larger dashboard, which we&rsquo;ll cover in the next section.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-2" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-2-1">Python</a></li>
<li><a href="#tabset-2-2">R</a></li>
</ul>
<div id="tabset-2-1">
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-2">
  <div class="code-with-filename-label" id="code-filename-2"><span class="font-mono text-sm">app.py</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny.express</span> <span class="kn">import</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">querychat.express</span> <span class="kn">import</span> <span class="n">QueryChat</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">palmerpenguins</span> <span class="kn">import</span> <span class="n">load_penguins</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">load_penguins</span><span class="p">(),</span> 
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;penguins&#34;</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">    <span class="n">client</span><span class="o">=</span><span class="s2">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">tools</span><span class="o">=</span><span class="p">(</span><span class="s2">&#34;query&#34;</span><span class="p">,</span> <span class="s2">&#34;visualize&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span><span class="o">.</span><span class="n">ui</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">page_opts</span><span class="p">(</span><span class="n">fillable</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span></span></span></code></pre></div></div>
<p>With <code>app.py</code> saved locally and your LLM running, start the app with:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">shiny run app.py</span></span></code></pre></div></div>
</div>
<div id="tabset-2-2">
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-4">
  <div class="code-with-filename-label" id="code-filename-4"><span class="font-mono text-sm">app.R</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">querychat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">palmerpenguins</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">penguins</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="s">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">tools</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;query&#34;</span><span class="p">,</span> <span class="s">&#34;visualize&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="n">bslib</span><span class="o">::</span><span class="nf">page_fillable</span><span class="p">(</span><span class="n">qc</span><span class="o">$</span><span class="nf">ui</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span> <span class="n">qc</span><span class="o">$</span><span class="nf">server</span><span class="p">()</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
<p>With <code>app.R</code> saved locally and your LLM running, start the app with:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">shiny</span><span class="o">::</span><span class="nf">runApp</span><span class="p">(</span><span class="s">&#34;app.R&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
</div>
<p>Upon opening the app, the LLM greets us with a welcome message tailored to the data source we&rsquo;ve provided:</p>
<img src="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/hello-chat.png" alt="Basic QueryChat interface to the Palmer Penguins dataset" class="shadow rounded" />
<h3 id="query-tool">Query tool
</h3>
<p>For questions addressable via numerical summaries, the LLM requests the <code>query</code> tool with relevant SQL. The LLM may also choose to collapse details of the tool call, especially when it decides to weave the results within its actual response. That said, full details can always be inspected by clicking on the &ldquo;Query Data&rdquo; tool call display:</p>
<img src="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/hello-query.png" alt="Query tool in action, showing the SQL query and the resulting summary in the chat response" class="shadow rounded" />
<h3 id="visualize-tool">Visualize tool
</h3>
<p>For questions that are better suited to a visual response, the LLM requests the <code>visualize</code> tool with relevant ggsql. In addition to rendering the plot inline for the user to see, that same plot is also provided to the LLM so that it can interpret the result:</p>
<img src="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/hello-viz.png" alt="Visualize tool in action, showing the ggsql query and the resulting chart in the chat response" class="shadow rounded" />
<p>A couple noteworthy things about this experience:</p>
<ul>
<li>The footer includes options to view the underlying ggsql query, download the plot as an image, and expand the display to full screen.</li>
<li>Plots are always shown by default, but can be collapsed by clicking the header.</li>
</ul>
<p>This hopefully gives you a taste for how easy it is to get set up with a basic <code>querychat</code> UI and start asking questions of your data.
You may, however, be wondering how <code>querychat</code> is able to produce correct queries and how the code execution actually works. Let&rsquo;s dive into those details next.</p>
<h2 id="how-does-it-work">How does it work?
</h2>
<h3 id="execution">Execution
</h3>
<p>One important thing to understand about <code>querychat</code> is that the LLM itself is not handling the SQL execution &ndash; <code>querychat</code> is. Where that execution ultimately happens depends on what data source you&rsquo;re providing. If you&rsquo;re pointing <code>querychat</code> at an in-memory data frame, the execution happens in-process using DuckDB. If you&rsquo;re pointing it at a database connection, the execution happens against that database. In either case, the LLM is generating queries based on its understanding of the data schema and any additional context you&rsquo;ve provided, not the actual data. This separation is what allows <code>querychat</code> to maintain a strong security posture, scale to large data, and deliver a good user experience.</p>
<h3 id="schema-discovery">Schema discovery
</h3>
<p>To elaborate on what &ldquo;understanding of the data schema&rdquo; actually means: when you point <code>querychat</code> at a data source, it automatically extracts column names, types, numerical ranges, and categorical values. This information is included in the system prompt for the LLM, so it has a clear picture of what the data looks like and can generate accurate queries without needing to see the actual data. For many datasets, this is enough to get good results out of the box.</p>
<h3 id="additional-context">Additional context
</h3>
<p>For more complex datasets or domain-specific questions, you can also provide additional context through a plain-text data description. <code>querychat</code> now also automatically picks up on <a href="https://www.snowflake.com/en/developers/guides/snowflake-semantic-view/" target="_blank" rel="noopener">Snowflake Semantic Models</a> when connected to a Snowflake database, giving the LLM access to authoritative definitions of business logic and metrics without any manual configuration. We hope to add more integrations to other semantic layer formats in the future.</p>
<p>To learn more, <code>querychat</code>&rsquo;s website has more details on <a href="https://posit-dev.github.io/querychat/py/data-sources.html" target="_blank" rel="noopener">data sources</a> (<a href="https://posit-dev.github.io/querychat/r/articles/data-sources.html" target="_blank" rel="noopener">R</a>), <a href="https://posit-dev.github.io/querychat/py/context.html" target="_blank" rel="noopener">providing context</a> (<a href="https://posit-dev.github.io/querychat/r/articles/context.html" target="_blank" rel="noopener">R</a>), and <a href="https://posit-dev.github.io/querychat/py/tools.html" target="_blank" rel="noopener">tool execution</a> (<a href="https://posit-dev.github.io/querychat/r/articles/tools.html" target="_blank" rel="noopener">R</a>).</p>
<h2 id="chat-driven-dashboards">Chat-driven dashboards
</h2>
<p>A chat interface alone likely isn&rsquo;t the experience you want to ship to end users &mdash; the better pattern is to surface interesting findings first, then let users explore beyond them. This is where <code>querychat</code> begins to really shine: the chat interface we already covered can easily be embedded inside of a larger app that includes other outputs &ndash; plots, tables, value boxes, etc. <code>querychat</code> comes with another tool designed for this use case &ndash; the filter tool &ndash; allowing the LLM to effectively drill down into relevant sections of the data in the dashboard in response to user questions.</p>
<p>The key integration point is <code>df()</code>, a reactive value that reflects the current state of the data after any filtering applied by the LLM. Use <code>df()</code> as the data source for your plots, tables, and value boxes, and they&rsquo;ll automatically update in response to user questions in the chat.</p>
<style>
style + .panel-tabset .highlight .chroma {
  max-height: 500px;
  overflow-y: auto !important;
}
</style>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-3" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-3-1">Python</a></li>
<li><a href="#tabset-3-2">R</a></li>
</ul>
<div id="tabset-3-1">
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-6">
  <div class="code-with-filename-label" id="code-filename-6"><span class="font-mono text-sm">app.py</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny.express</span> <span class="kn">import</span> <span class="n">render</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">querychat.express</span> <span class="kn">import</span> <span class="n">QueryChat</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">palmerpenguins</span> <span class="kn">import</span> <span class="n">load_penguins</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">faicons</span> <span class="kn">import</span> <span class="n">icon_svg</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">load_penguins</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;penguins&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">client</span><span class="o">=</span><span class="s2">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">tools</span><span class="o">=</span><span class="p">(</span><span class="s2">&#34;filter&#34;</span><span class="p">,</span> <span class="s2">&#34;query&#34;</span><span class="p">,</span> <span class="s2">&#34;visualize&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span><span class="o">.</span><span class="n">sidebar</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">(</span><span class="n">fill</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">value_box</span><span class="p">(</span><span class="n">showcase</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;binoculars&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Penguins&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">count</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">qc</span><span class="o">.</span><span class="n">df</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">value_box</span><span class="p">(</span><span class="n">showcase</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;fingerprint&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Species&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">species</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">qc</span><span class="o">.</span><span class="n">df</span><span class="p">()[</span><span class="s2">&#34;species&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">nunique</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">value_box</span><span class="p">(</span><span class="n">showcase</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;gauge-high&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Avg Body Mass&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">avg_mass</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">qc</span><span class="o">.</span><span class="n">df</span><span class="p">()[</span><span class="s1">&#39;body_mass_g&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">mean</span><span class="p">()</span><span class="si">:</span><span class="s2">.0f</span><span class="si">}</span><span class="s2">g&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="s2">&#34;Bill Dimensions&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nd">@render.plot</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">scatter</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="kn">import</span> <span class="nn">plotnine</span> <span class="k">as</span> <span class="nn">p9</span>
</span></span><span class="line"><span class="cl">            
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">p9</span><span class="o">.</span><span class="n">ggplot</span><span class="p">(</span><span class="n">qc</span><span class="o">.</span><span class="n">df</span><span class="p">(),</span> <span class="n">p9</span><span class="o">.</span><span class="n">aes</span><span class="p">(</span><span class="s2">&#34;bill_length_mm&#34;</span><span class="p">,</span> <span class="s2">&#34;bill_depth_mm&#34;</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s2">&#34;species&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="o">+</span> <span class="n">p9</span><span class="o">.</span><span class="n">geom_point</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="s2">&#34;Measurements&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nd">@render.data_frame</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">table</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">qc</span><span class="o">.</span><span class="n">df</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">page_opts</span><span class="p">(</span><span class="n">fillable</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
<div id="tabset-3-2">
<div class="code-block code-with-filename" role="group" aria-labelledby="code-filename-7">
  <div class="code-with-filename-label" id="code-filename-7"><span class="font-mono text-sm">app.R</span></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">querychat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">DT</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ggplot2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">palmerpenguins</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">penguins</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="s">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">tools</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;filter&#34;</span><span class="p">,</span> <span class="s">&#34;query&#34;</span><span class="p">,</span> <span class="s">&#34;visualize&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">sidebar</span> <span class="o">=</span> <span class="n">qc</span><span class="o">$</span><span class="nf">sidebar</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">fill</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nf">value_box</span><span class="p">(</span><span class="s">&#34;Penguins&#34;</span><span class="p">,</span> <span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;count&#34;</span><span class="p">),</span> <span class="n">showcase</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;binoculars&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">value_box</span><span class="p">(</span><span class="s">&#34;Species&#34;</span><span class="p">,</span> <span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;species&#34;</span><span class="p">),</span> <span class="n">showcase</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;fingerprint&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">value_box</span><span class="p">(</span><span class="s">&#34;Avg Body Mass&#34;</span><span class="p">,</span> <span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;avg_mass&#34;</span><span class="p">),</span> <span class="n">showcase</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;speedometer&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span><span class="nf">card_header</span><span class="p">(</span><span class="s">&#34;Bill Dimensions&#34;</span><span class="p">),</span> <span class="nf">plotOutput</span><span class="p">(</span><span class="s">&#34;scatter&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card</span><span class="p">(</span><span class="nf">card_header</span><span class="p">(</span><span class="s">&#34;Measurements&#34;</span><span class="p">),</span> <span class="nf">DTOutput</span><span class="p">(</span><span class="s">&#34;table&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">qc_vals</span> <span class="o">&lt;-</span> <span class="n">qc</span><span class="o">$</span><span class="nf">server</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="n">df</span> <span class="o">&lt;-</span> <span class="n">qc_vals</span><span class="o">$</span><span class="n">df</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">count</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">(</span><span class="nf">nrow</span><span class="p">(</span><span class="nf">df</span><span class="p">()))</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">species</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">(</span><span class="nf">length</span><span class="p">(</span><span class="nf">unique</span><span class="p">(</span><span class="nf">df</span><span class="p">()</span><span class="o">$</span><span class="n">species</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">avg_mass</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">(</span><span class="nf">paste0</span><span class="p">(</span><span class="nf">round</span><span class="p">(</span><span class="nf">mean</span><span class="p">(</span><span class="nf">df</span><span class="p">()</span><span class="o">$</span><span class="n">body_mass_g</span><span class="p">,</span> <span class="n">na.rm</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">)),</span> <span class="s">&#34;g&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">scatter</span> <span class="o">&lt;-</span> <span class="nf">renderPlot</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">ggplot</span><span class="p">(</span><span class="nf">df</span><span class="p">(),</span> <span class="nf">aes</span><span class="p">(</span><span class="n">bill_length_mm</span><span class="p">,</span> <span class="n">bill_depth_mm</span><span class="p">,</span> <span class="n">color</span> <span class="o">=</span> <span class="n">species</span><span class="p">))</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">      <span class="nf">geom_point</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">table</span> <span class="o">&lt;-</span> <span class="nf">renderDT</span><span class="p">(</span><span class="nf">df</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span></span></span></code></pre></div></div>
</div>
</div>
<img src="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/dashboard.png" alt="QueryChat embedded in a dashboard with value boxes, a scatter plot, and a data table — all reactively driven by the chat's filter state" class="shadow rounded" />
<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Python users: Shiny v Streamlit/Dash/Gradio</span>
</div>
<div class="callout-body">
<p><code>querychat</code> also supports <code>streamlit</code>, <code>dash</code>, and <code>gradio</code>. That said, the reactive <code>df()</code> pattern shown above &mdash; where the LLM&rsquo;s filter state automatically drives every view in the app &mdash; is what Shiny&rsquo;s reactive programming model was designed for. In other frameworks, keeping the chat&rsquo;s state in sync with multiple outputs typically requires more manual wiring.</p>
</div>
</div>
<h2 id="custom-tools">Custom tools
</h2>
<p><code>querychat</code> comes with three built-in tools, but you can also easily add your own custom tools. This is possible thanks to the extensible foundation provided by <a href="https://posit-dev.github.io/chatlas/" target="_blank" rel="noopener">chatlas</a> (<a href="https://ellmer.tidyverse.org" target="_blank" rel="noopener">ellmer</a>). These packages also make it quite easy to implement tools &ndash; all you really need is a Python / R function that performs some operation. You can also fully customize the display shown, thanks to their rich support for <a href="https://posit-dev.github.io/chatlas/tool-calling/displays.html" target="_blank" rel="noopener">tool call displays</a> (<a href="https://posit-dev.github.io/shinychat/r/articles/tool-ui.html" target="_blank" rel="noopener">R</a>).</p>
<p>To give you a sense of what other capabilities are possible, you could start out as simple as querying <a href="https://posit-dev.github.io/chatlas/get-started/tools.html" target="_blank" rel="noopener">real-time weather information</a>, but also get as sophisticated as a <a href="https://posit-dev.github.io/chatlas/misc/RAG.html#dynamic-retrieval" target="_blank" rel="noopener">RAG-like knowledge retrieval agent</a>. For example, here&rsquo;s how you&rsquo;d let users ask whether weather conditions might relate to trends in your data:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-4" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-4-1">Python</a></li>
<li><a href="#tabset-4-2">R</a></li>
</ul>
<div id="tabset-4-1">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">querychat.express</span> <span class="kn">import</span> <span class="n">QueryChat</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatLMStudio</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">palmerpenguins</span> <span class="kn">import</span> <span class="n">load_penguins</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat_client</span> <span class="o">=</span> <span class="n">ChatLMStudio</span><span class="p">(</span><span class="n">model</span><span class="o">=</span><span class="s2">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_current_weather</span><span class="p">(</span><span class="n">lat</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span> <span class="n">lng</span><span class="p">:</span> <span class="nb">float</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Get the current weather for a location.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">lat_lng</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;latitude=</span><span class="si">{</span><span class="n">lat</span><span class="si">}</span><span class="s2">&amp;longitude=</span><span class="si">{</span><span class="n">lng</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;https://api.open-meteo.com/v1/forecast?</span><span class="si">{</span><span class="n">lat_lng</span><span class="si">}</span><span class="s2">&amp;current=temperature_2m,wind_speed_10m&amp;hourly=temperature_2m,relative_humidity_2m,wind_speed_10m&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()[</span><span class="s2">&#34;current&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat_client</span><span class="o">.</span><span class="n">register_tool</span><span class="p">(</span><span class="n">get_current_weather</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">load_penguins</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;penguins&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">client</span><span class="o">=</span><span class="n">chat_client</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
</div>
<div id="tabset-4-2">
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">querychat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat_client</span> <span class="o">&lt;-</span> <span class="nf">chat_lmstudio</span><span class="p">(</span><span class="n">model</span> <span class="o">=</span> <span class="s">&#34;lmstudio/google/gemma-4-26b-a4b&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">get_current_weather</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lng</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">lat_lng</span> <span class="o">&lt;-</span> <span class="nf">paste0</span><span class="p">(</span><span class="s">&#34;latitude=&#34;</span><span class="p">,</span> <span class="n">lat</span><span class="p">,</span> <span class="s">&#34;&amp;longitude=&#34;</span><span class="p">,</span> <span class="n">lng</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">url</span> <span class="o">&lt;-</span> <span class="nf">paste0</span><span class="p">(</span><span class="s">&#34;https://api.open-meteo.com/v1/forecast?&#34;</span><span class="p">,</span> <span class="n">lat_lng</span><span class="p">,</span> <span class="s">&#34;&amp;current=temperature_2m,wind_speed_10m&amp;hourly=temperature_2m,relative_humidity_2m,wind_speed_10m&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">response</span> <span class="o">&lt;-</span> <span class="n">httr</span><span class="o">::</span><span class="nf">GET</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">httr</span><span class="o">::</span><span class="nf">content</span><span class="p">(</span><span class="n">response</span><span class="p">)</span><span class="o">$</span><span class="n">current</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat_client</span><span class="o">$</span><span class="nf">register_tool</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">tool</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">get_current_weather</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Get the current weather for a location&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">lat</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span><span class="s">&#34;Latitude&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">lng</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span><span class="s">&#34;Longitude&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">palmerpenguins</span><span class="o">::</span><span class="n">penguins</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="n">chat_client</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
</div>
</div>
<p>Underneath the hood, <code>querychat</code>&rsquo;s built-in tools use this same foundation. The &ldquo;magic&rdquo; of these tools really breaks down to three key insights:</p>
<ol>
<li>LLMs are very good at translating natural language into code.</li>
<li>Tool call arguments can be executable code (like SQL or ggsql) rather than just simple values.</li>
<li>Tool calls can <a href="https://shiny.posit.co/py/docs/genai-tools.html#managing-state" target="_blank" rel="noopener">manage reactive state in a Shiny app</a> &mdash; for <code>querychat</code>, that state is the current filter query, but the same pattern could control input controls, tab selection, or other UI state.</li>
</ol>
<h2 id="whats-next">What&rsquo;s next?
</h2>
<p>This release is just the beginning of our vision for (responsibly) exploring data via natural language. We&rsquo;re excited to see what you build with it, and we&rsquo;re already thinking about the next set of features to add. Some things on our near-term roadmap:</p>
<ul>
<li>Multiple tables.</li>
<li>Reproducible takeaway artifacts, like <a href="https://quarto.org/docs/dashboards/" target="_blank" rel="noopener">Quarto dashboards</a></li>
<li>Support for more semantic layer solutions beyond Snowflake, so more users can give the LLM authoritative definitions of their business logic.</li>
</ul>
<h2 id="learn-more">Learn more
</h2>
<ul>
<li><a href="https://posit-dev.github.io/querychat/py/" target="_blank" rel="noopener">querychat documentation</a> (<a href="https://posit-dev.github.io/querychat/r/" target="_blank" rel="noopener">R</a>) &mdash; full guides on data sources, context, tools, and deployment</li>
<li><a href="https://ggsql.org" target="_blank" rel="noopener">ggsql</a> &mdash; the grammar of graphics for SQL that powers querychat&rsquo;s visualizations</li>
<li><a href="https://posit-dev.github.io/chatlas/" target="_blank" rel="noopener">chatlas</a> (<a href="https://ellmer.tidyverse.org" target="_blank" rel="noopener">ellmer</a>) &mdash; the underlying LLM tool-calling libraries, useful for building custom tools</li>
<li><a href="https://posit-dev.github.io/shinychat/py/" target="_blank" rel="noopener">shinychat</a> (<a href="https://posit-dev.github.io/shinychat/r/" target="_blank" rel="noopener">R</a>) &mdash; the chat UI component that querychat builds on</li>
<li><a href="https://shiny.posit.co/py/" target="_blank" rel="noopener">Shiny</a> (<a href="https://shiny.posit.co/r/" target="_blank" rel="noopener">R</a>) &mdash; the web framework powering querychat apps</li>
<li><a href="https://github.com/posit-dev/querychat" target="_blank" rel="noopener">Source on GitHub</a> &mdash; issues, discussions, and contributions welcome</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://gist.github.com/cpsievert/ca0d7e4637fdf994671c9d9fc90cd89f" target="_blank" rel="noopener">Source code</a> is available for reference, though you&rsquo;ll need your own Snowflake account and credentials to run it.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-17_querychat-ggsql/featured.png" length="454060" type="image/png" />
    </item>
    <item>
      <title>dbplyr 2.6.0</title>
      <link>https://opensource.posit.co/blog/2026-06-17_dbplyr-2-6-0/</link>
      <pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-17_dbplyr-2-6-0/</guid>
      <dc:creator>Hadley Wickham</dc:creator><description><![CDATA[<p>I&rsquo;m pleased to announce that <a href="https://dbplyr.tidyverse.org" target="_blank" rel="noopener">dbplyr 2.6.0</a> is now on CRAN.
dbplyr is the database backend for <a href="https://dplyr.tidyverse.org" target="_blank" rel="noopener">dplyr</a>: it automatically translates the dplyr code you&rsquo;d write for a data frame into SQL so you can run the same pipeline against tables in a remote database.
You can install it with:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;dbplyr&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>This is another release that has benefited from Claude Code&rsquo;s help, allowing me to rip through a large number of smaller issues (~50!) where the correct solution is easily verified.
And those time savings allowed me to tackle some more challenging and more impactful features, such as new ADBC and JDBC backends and a new <code>sql_dialect()</code> generic that finally separates <em>how you connect to a database</em> from <em>which SQL dialect to use</em>.
The release also includes new translations, a couple of useful helpers, and a substantial cleanup of long-standing deprecations.
This post covers the highlights for everyday users in the first half, then dives into the changes that matter for backend developers at the end.
Read the full list of changes in the <a href="https://github.com/tidyverse/dbplyr/releases/tag/v2.6.0" target="_blank" rel="noopener">release notes</a>.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">dbplyr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">dplyr</span><span class="p">,</span> <span class="n">warn.conflicts</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">)</span></span></span></code></pre></div></div>
<h2 id="lifecycle-changes">Lifecycle changes
</h2>
<p>A handful of things you may have been using have been deprecated or removed.
The biggest ones to know about:</p>
<ul>
<li>
<p><strong><code>do()</code> is deprecated.</strong> It was the only piece of dplyr 0.x syntax that dbplyr still supported, and it never fit cleanly into SQL.
If you have code that uses it, the simplest path is to <code>collect()</code> first and then use your favourite tidyverse tools on the resulting data frame.</p>
</li>
<li>
<p><strong><code>str_like(ignore_case = TRUE)</code> is deprecated</strong> in favour of the new <code>str_ilike()</code> (thanks to <a href="https://github.com/edward-burn" target="_blank" rel="noopener">@edward-burn</a>).
This brings the dbplyr translation in line with the recent updates to <a href="https://stringr.tidyverse.org" target="_blank" rel="noopener">stringr</a>.</p>
</li>
<li>
<p><strong>The <code>cte</code> argument</strong> of <code>collect()</code>, <code>compute()</code>, <code>show_query()</code>, <code>remote_query()</code>, and <code>db_sql_render()</code> is deprecated.
Pass <code>sql_options = sql_options(cte = TRUE)</code> instead.
This consolidates a growing pile of one-off flags into a single options object that we can extend without tweaking every render function.</p>
</li>
</ul>
<p>A few deprecations that have been warning for multiple years are now defunct: passing <code>...</code> to <code>across()</code>/<code>if_all()</code>/<code>if_any()</code>, using <code>by = character()</code> to perform a cross join (use <code>cross_join()</code> instead), and calling <code>compute(temporary = FALSE)</code> without a <code>name</code>.
A few defunct functions/arguments have been removed entirely, including <code>group_by(add = )</code>, <code>partial_eval(var)</code>, and <code>src_sql()</code>.</p>
<p>If you&rsquo;re a backend developer, there&rsquo;s a longer list of changes that affect you &mdash; see the <a href="#notes-for-backend-developers">Notes for backend developers</a> at the end of this post.</p>
<h2 id="new-backends-adbc-and-jdbc">New backends: ADBC and JDBC
</h2>
<p>dbplyr already worked with ODBC (thanks to {<a href="https://odbc.r-dbi.org" target="_blank" rel="noopener">odbc</a>}).
With 2.6.0, you can now connect through two more transport layers:</p>
<ul>
<li>
<p><strong><a href="https://arrow.apache.org/adbc/" target="_blank" rel="noopener">ADBC</a></strong> (Arrow Database Connectivity) is a relatively new project from the Apache Arrow community.
It moves data between R and the database as Arrow buffers, which avoids the per-row serialization overhead of older protocols and can be much (much!) faster.
dbplyr supports ADBC connections through {<a href="https://github.com/r-dbi/adbi" target="_blank" rel="noopener">adbi</a>}.</p>
</li>
<li>
<p><strong><a href="https://en.wikipedia.org/wiki/Java_Database_Connectivity" target="_blank" rel="noopener">JDBC</a></strong> (Java Database Connectivity) is the standard database protocol on the JVM, with high-quality drivers for nearly every database under the sun.
dbplyr supports JDBC connections through {<a href="https://github.com/s-u/RJDBC" target="_blank" rel="noopener">RJDBC</a>}.</p>
</li>
</ul>
<p>The reason these new backends slot in so cleanly is a new generic, <code>sql_dialect()</code>.
Until now, dbplyr picked SQL translations based on the connection class itself, which meant every new transport had to either masquerade as an existing connection or carry its own copy of every translation.
(Or as with {odbc}, hack the database name into the object class.)
That&rsquo;s why ADBC and JDBC support was hard to add in the past.</p>
<p><code>sql_dialect()</code> separates the two concerns: a connection&rsquo;s job is to know how to talk to the server and fetch data, and the dialect&rsquo;s job is to know how to generate the right SQL.
For example, when you connect over JDBC or ADBC to Postgres, dbplyr now uses the same translations it would use over ODBC or via <a href="https://rpostgres.r-dbi.org" target="_blank" rel="noopener">RPostgres</a>.
This simplified a bunch of dbplyr code and made it very easy to add the new ADBC and JDBC backends.
Finally, if the default dialect isn&rsquo;t quite right, you can override it with <code>with_dialect()</code>.</p>
<h2 id="new-translations">New translations
</h2>
<p>A few translations are now available.</p>
<p><code>bind_queries()</code> is the dbplyr equivalent of <code>dplyr::bind_rows()</code>: it combines several lazy queries into one using <code>UNION ALL</code>, aligning columns as needed.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">con</span> <span class="o">&lt;-</span> <span class="n">DBI</span><span class="o">::</span><span class="nf">dbConnect</span><span class="p">(</span><span class="n">RSQLite</span><span class="o">::</span><span class="nf">SQLite</span><span class="p">(),</span> <span class="s">&#34;:memory:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">db1</span> <span class="o">&lt;-</span> <span class="nf">copy_to</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">tibble</span><span class="o">::</span><span class="nf">tibble</span><span class="p">(</span><span class="n">x</span> <span class="o">=</span> <span class="m">1</span><span class="o">:</span><span class="m">3</span><span class="p">),</span> <span class="s">&#34;db1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">db2</span> <span class="o">&lt;-</span> <span class="nf">copy_to</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">tibble</span><span class="o">::</span><span class="nf">tibble</span><span class="p">(</span><span class="n">y</span> <span class="o">=</span> <span class="m">1</span><span class="o">:</span><span class="m">3</span><span class="p">,</span> <span class="n">x</span> <span class="o">=</span> <span class="m">4</span><span class="o">:</span><span class="m">6</span><span class="p">),</span> <span class="s">&#34;db2&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">bind_queries</span><span class="p">(</span><span class="n">db1</span><span class="p">,</span> <span class="n">db2</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="nf">show_query</span><span class="p">()</span></span></span></code></pre></div></div>
<pre><code>&lt;SQL&gt;
SELECT *, NULL AS `y`
FROM `db1`

UNION ALL

SELECT `x`, `y`
FROM `db2`
</code></pre>
<p><code>filter_out()</code> (from <a href="https://opensource.posit.co/blog/2026-02-04_dplyr-1-2-0">dplyr 1.2.0</a>) is now translated.
It&rsquo;s the opposite of <code>filter()</code>, dropping rows where the conditions are true, rather than keeping those rows.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">db3</span> <span class="o">&lt;-</span> <span class="nf">copy_to</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">tibble</span><span class="o">::</span><span class="nf">tibble</span><span class="p">(</span><span class="n">x</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="kc">NA</span><span class="p">),</span> <span class="n">g</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">)),</span> <span class="s">&#34;db3&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">db3</span> <span class="o">|&gt;</span> <span class="nf">filter_out</span><span class="p">(</span><span class="n">x</span> <span class="o">==</span> <span class="m">2</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code># A query:  ?? x 2
# Database: sqlite 3.52.0 [:memory:]
      x     g
  &lt;dbl&gt; &lt;dbl&gt;
1     1     1
2    NA     2
</code></pre>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">db3</span> <span class="o">|&gt;</span> <span class="nf">filter</span><span class="p">(</span><span class="n">x</span> <span class="o">==</span> <span class="m">2</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code># A query:  ?? x 2
# Database: sqlite 3.52.0 [:memory:]
      x     g
  &lt;dbl&gt; &lt;dbl&gt;
1     2     1
</code></pre>
<p><code>anyNA()</code> is now translated, in the same way as <code>any(is.na(x))</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">db3</span> <span class="o">|&gt;</span> <span class="nf">summarise</span><span class="p">(</span><span class="n">missing</span> <span class="o">=</span> <span class="nf">anyNA</span><span class="p">(</span><span class="n">x</span><span class="p">),</span> <span class="n">.by</span> <span class="o">=</span> <span class="n">g</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="nf">show_query</span><span class="p">()</span></span></span></code></pre></div></div>
<pre><code>&lt;SQL&gt;
SELECT `g`, MAX((`x` IS NULL)) AS `missing`
FROM `db3`
GROUP BY `g`
</code></pre>
<p>And <code>%notin%</code> (new in R 4.6.0) is now translated to <code>NOT IN</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">db3</span> <span class="o">|&gt;</span> <span class="nf">filter</span><span class="p">(</span><span class="n">x</span> <span class="o">%notin%</span> <span class="nf">c</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">))</span> <span class="o">|&gt;</span> <span class="nf">show_query</span><span class="p">()</span></span></span></code></pre></div></div>
<pre><code>&lt;SQL&gt;
SELECT *
FROM `db3`
WHERE (`x` NOT IN (1.0, 2.0))
</code></pre>
<p>Three other changes are worth knowing about:</p>
<ul>
<li>
<p><strong><code>last_sql()</code></strong> returns the SQL of the most recent query dbplyr generated.
This is mostly useful when debugging a problem that surfaces inside <code>collect()</code> or <code>compute()</code>, when you don&rsquo;t have a convenient handle on the query to call <code>show_query()</code> on.</p>
</li>
<li>
<p><strong><code>copy = &quot;inline&quot;</code></strong> is now an option for joins, set operations, and row operations.
When you join a local data frame to a remote table, dbplyr normally copies it to a temporary table on the server.
With <code>copy = &quot;inline&quot;</code>, it inlines the data into the SQL directly using <code>copy_inline()</code>, which is handy when you can&rsquo;t (or don&rsquo;t want to) create a temporary table.</p>
</li>
<li>
<p>Thanks to <a href="https://github.com/shearerpmm" target="_blank" rel="noopener">@shearerpmm</a>, this release also gains a translation layer for IBM DB2.
The translation includes <code>paste()</code>/<code>paste0()</code> (using <code>||</code>), DB2-specific casts, <code>runif()</code>, a comprehensive set of string and date functions, the <a href="https://clock.r-lib.org" target="_blank" rel="noopener">clock</a> helpers, and statistical aggregates.</p>
</li>
</ul>
<h2 id="notes-for-backend-developers">Notes for backend developers
</h2>
<p>If you maintain a dbplyr backend, several things have changed and you may need to make some adjustments.
Hopefully none of this is a surprise, since I&rsquo;ve already filed PRs to all CRAN packages that needed changes 😀.</p>
<p>Most importantly, the new <code>sql_dialect()</code> generic separates out the connection details from SQL generation.
You should create a <code>sql_dialect()</code> method for your connection that returns a <code>new_sql_dialect()</code> object.
<code>new_sql_dialect()</code> lets you customize the full surface of SQL generation, including how identifiers are quoted (which allows tests to look much closer to real SQL).
Once you have that object, all the <code>sql_</code> generics can dispatch on it, rather than on the connection object.</p>
<p>There are two new extension points for customization:</p>
<ul>
<li>
<p><code>db_table_drop_if_exists()</code> is a new generic that lets backends customize how tables are dropped when <code>overwrite = TRUE</code>.
It was added to support Oracle, which needs a slightly different <code>DROP TABLE</code> incantation.</p>
</li>
<li>
<p><code>sql_set_op_method()</code> is a new generic that lets set operations (<code>union()</code>, <code>intersect()</code>, <code>setdiff()</code>) customize the SQL keyword they generate.
Useful when a backend needs <code>UNION DISTINCT</code> instead of <code>UNION</code>, or <code>MINUS</code> instead of <code>EXCEPT</code>.</p>
</li>
</ul>
<p>And two deprecations:</p>
<ul>
<li>
<p><code>sql_expr_matches()</code> is deprecated. Provide <code>is_distinct_from()</code> and <code>is_not_distinct_from()</code> translations instead. These power joins with <code>na_matches = &quot;na&quot;</code> and the new <code>filter_out()</code> translation.</p>
</li>
<li>
<p><code>as.sql()</code> is deprecated as part of a major internal refactor of how <code>sql()</code> and <code>ident()</code> are used. You can generally replace it with <code>as_table_path()</code> if used to refer to a table, or <code>sql()</code> if you want to indicate it&rsquo;s raw SQL.</p>
</li>
</ul>
<p>I&rsquo;ve also overhauled the exported tools for generating SQL strings. The newly exported <code>sql_glue()</code> (implicit dialect, for use inside <code>sql_translation()</code>) and <code>sql_glue2()</code> (explicit dialect, for everywhere else) provide a glue-style syntax for building SQL strings. They replace the now superseded <code>build_sql()</code>, <code>sql_expr()</code>, and <code>sql_call2()</code>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-17_dbplyr-2-6-0/featured.jpg" length="281420" type="image/jpeg" />
    </item>
    <item>
      <title>tidyclust 0.3.0</title>
      <link>https://opensource.posit.co/blog/2026-06-15_tidyclust-0-3-0/</link>
      <pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-15_tidyclust-0-3-0/</guid>
      <dc:creator>Emil Hvitfeldt</dc:creator><description><![CDATA[<p>I&rsquo;m thrilled to announce that <a href="https://tidyclust.tidymodels.org/" target="_blank" rel="noopener">tidyclust 0.3.0</a> is now on CRAN.
tidyclust allows you to fit, interact, and evaluate unsupervised clustering models inside the tidymodels framework.
You can install it with:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;tidyclust&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>This release brings three new families of clustering models,
closer alignment with the rest of tidymodels,
and a number of smaller improvements.
This post covers the highlights of new models and engines,
tighter integration with tidymodels,
and updated parallel processing support.
You can read the full list of changes in the <a href="https://tidyclust.tidymodels.org/news/index.html#tidyclust-030" target="_blank" rel="noopener">release notes</a>.</p>
<p>To get started,
load the tidymodels and tidyclust packages:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">tidymodels</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">tidyclust</span><span class="p">)</span></span></span></code></pre></div></div>
<p>For the examples, we will be using the <code>penguins</code> data set.
We are imputing missing values and normalizing the predictors since distance and density-based methods are sensitive to scale and missingness.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">data</span><span class="p">(</span><span class="n">penguins</span><span class="p">,</span> <span class="n">package</span> <span class="o">=</span> <span class="s">&#34;modeldata&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">rec_spec</span> <span class="o">&lt;-</span> <span class="nf">recipe</span><span class="p">(</span><span class="o">~</span> <span class="n">bill_length_mm</span> <span class="o">+</span> <span class="n">bill_depth_mm</span> <span class="o">+</span> <span class="n">flipper_length_mm</span> <span class="o">+</span> <span class="n">body_mass_g</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                   <span class="n">data</span> <span class="o">=</span> <span class="n">penguins</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">step_impute_mean</span><span class="p">(</span><span class="nf">all_numeric_predictors</span><span class="p">())</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">step_normalize</span><span class="p">(</span><span class="nf">all_numeric_predictors</span><span class="p">())</span></span></span></code></pre></div></div>
<h2 id="new-models-and-engines">New models and engines
</h2>
<p>Until now, tidyclust supported partition-based methods (<code>k_means()</code>) and hierarchical clustering (<code>hier_clust()</code>).
This release expands that considerably with three new model specifications.</p>
<p><a href="https://tidyclust.tidymodels.org/reference/db_clust.html" target="_blank" rel="noopener"><code>db_clust()</code></a> fits density-based clustering models (DBSCAN),
with engines for both <code>&quot;dbscan&quot;</code> and <code>&quot;hdbscan&quot;</code>.
Density-based methods are well suited to finding clusters of arbitrary shape and identifying outliers as noise.</p>
<p>To use tidyclust,
we first make a specification for the clustering method,
combine it with the recipe, and fit the workflow as usual.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">db_spec</span> <span class="o">&lt;-</span> <span class="nf">db_clust</span><span class="p">(</span><span class="n">radius</span> <span class="o">=</span> <span class="m">0.8</span><span class="p">,</span> <span class="n">min_points</span> <span class="o">=</span> <span class="m">5</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">set_engine</span><span class="p">(</span><span class="s">&#34;dbscan&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">db_fit</span> <span class="o">&lt;-</span> <span class="nf">workflow</span><span class="p">(</span><span class="n">rec_spec</span><span class="p">,</span> <span class="n">db_spec</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">fit</span><span class="p">(</span><span class="n">data</span> <span class="o">=</span> <span class="n">penguins</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">db_fit</span></span></span></code></pre></div></div>
<pre><code>══ Workflow [trained] ══════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: db_clust()

── Preprocessor ────────────────────────────────────────────────────────────────
2 Recipe Steps

• step_impute_mean()
• step_normalize()

── Model ───────────────────────────────────────────────────────────────────────
DBSCAN clustering for 344 objects.
Parameters: eps = 0.8, minPts = 5
Using euclidean distances and borderpoints = TRUE
The clustering contains 2 cluster(s) and 5 noise points.

  0   1   2 
  5 218 121 

Available fields: cluster, eps, minPts, metric, borderPoints
</code></pre>
<p>Notice the <code>Outlier</code> level: points in low-density regions aren&rsquo;t forced into a cluster.
This is also our first new engine where you don&rsquo;t specify how many clusters to find up front.</p>
<p><a href="https://tidyclust.tidymodels.org/reference/gm_clust.html" target="_blank" rel="noopener"><code>gm_clust()</code></a> fits Gaussian mixture models via the <code>&quot;mclust&quot;</code> engine.
Rather than assigning each observation to a single cluster,
mixture models describe the data as a combination of Gaussian components.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">gm_spec</span> <span class="o">&lt;-</span> <span class="nf">gm_clust</span><span class="p">(</span><span class="n">num_clusters</span> <span class="o">=</span> <span class="m">3</span><span class="p">)</span> <span class="o">|&gt;</span> 
</span></span><span class="line"><span class="cl">  <span class="nf">set_engine</span><span class="p">(</span><span class="s">&#34;mclust&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">gm_fit</span> <span class="o">&lt;-</span> <span class="nf">workflow</span><span class="p">(</span><span class="n">rec_spec</span><span class="p">,</span> <span class="n">gm_spec</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">fit</span><span class="p">(</span><span class="n">data</span> <span class="o">=</span> <span class="n">penguins</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">tidy</span><span class="p">(</span><span class="n">gm_fit</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code># A tibble: 3 × 8
  component  size proportion variance mean.bill_length_mm mean.bill_depth_mm
      &lt;int&gt; &lt;int&gt;      &lt;dbl&gt;    &lt;dbl&gt;               &lt;dbl&gt;              &lt;dbl&gt;
1         1   160      0.458    0.288              -0.892              0.524
2         2    61      0.184    0.288               0.938              0.834
3         3   123      0.358    0.288               0.658             -1.10 
# ℹ 2 more variables: mean.flipper_length_mm &lt;dbl&gt;, mean.body_mass_g &lt;dbl&gt;
</code></pre>
<p>Each row summarizes one of the fitted Gaussian components:
its size, mixing proportion, and the per-predictor means that describe where it sits.</p>
<p><a href="https://tidyclust.tidymodels.org/reference/mean_shift.html" target="_blank" rel="noopener"><code>mean_shift()</code></a> fits mean shift models,
which iteratively shift observations toward regions of high density and determine the number of clusters automatically.
Engines <code>&quot;LPCM&quot;</code> and <code>&quot;meanShiftR&quot;</code> are supported.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">ms_spec</span> <span class="o">&lt;-</span> <span class="nf">mean_shift</span><span class="p">(</span><span class="n">bandwidth</span> <span class="o">=</span> <span class="m">0.2</span><span class="p">)</span> <span class="o">|&gt;</span> 
</span></span><span class="line"><span class="cl">  <span class="nf">set_engine</span><span class="p">(</span><span class="s">&#34;LPCM&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ms_fit</span> <span class="o">&lt;-</span> <span class="nf">workflow</span><span class="p">(</span><span class="n">rec_spec</span><span class="p">,</span> <span class="n">ms_spec</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">fit</span><span class="p">(</span><span class="n">data</span> <span class="o">=</span> <span class="n">penguins</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">extract_cluster_assignment</span><span class="p">(</span><span class="n">ms_fit</span><span class="p">)</span></span></span></code></pre></div></div>
<pre><code># A tibble: 344 × 1
   .cluster 
   &lt;fct&gt;    
 1 Cluster_1
 2 Cluster_1
 3 Cluster_1
 4 Cluster_1
 5 Cluster_1
 6 Cluster_1
 7 Cluster_1
 8 Cluster_1
 9 Cluster_1
10 Cluster_1
# ℹ 334 more rows
</code></pre>
<p>This returns a cluster label for each observation,
with the number of clusters discovered automatically rather than set in advance like how it was in <code>db_clust()</code>.</p>
<p>Each of these models comes with its respective <code>dials</code> parameters for tuning.</p>
<h2 id="closer-alignment-with-tidymodels">Closer alignment with tidymodels
</h2>
<p>A major theme of this release is removing the seams between tidyclust and the rest of tidymodels.
Tidyclust was previously written as a slightly modified copy of the internals of the tune package.
We took some time to align the internals of tune such that we could use the same code paths for tidyclust and tune.
This means that we have a smaller maintenance burden and new features in tune should be more easily available in tidyclust.</p>
<p>The biggest change is that <code>finalize_model_tidyclust()</code> and <code>finalize_workflow_tidyclust()</code> are now deprecated.
The corresponding functions in tune <code>tune::finalize_model()</code> and <code>tune::finalize_workflow()</code> now support <code>cluster_spec</code> objects natively,
so there is no longer a need for tidyclust-specific variants.</p>
<p>Parallel processing in <code>tune_cluster()</code> now supports the <a href="https://mirai.r-lib.org/" target="_blank" rel="noopener">mirai</a> package in addition to <code>future</code>.
This matches the parallelism approach used across tidymodels.
As part of this change,
the <code>foreach</code> package is no longer supported for parallel processing&mdash;use <code>future</code> or <code>mirai</code> instead.
The <code>.config</code> column produced by <code>tune_cluster()</code> has also changed from the <code>Preprocessor{num}_Model{num}</code> pattern to <code>pre{num}_mod{num}_post{num}</code> to align with updates in tune.</p>
<p>A few other improvements worth calling out:</p>
<ul>
<li>A new <a href="https://tidyclust.tidymodels.org/articles/tidyclust.html" target="_blank" rel="noopener">&ldquo;Getting started with tidyclust&rdquo;</a> vignette.</li>
<li><code>butcher</code> support for <code>cluster_fit</code> objects, so you can strip training data and environment references from fitted models before saving them.</li>
<li><code>extract_cluster_assignment()</code>, <code>extract_centroids()</code>, and <code>predict()</code> gain a <code>labels</code> argument for supplying custom cluster labels.</li>
<li><code>hier_clust()</code> gains a <code>dist_fun</code> argument for specifying a custom distance function.</li>
</ul>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>A big thank you to everyone who has contributed issues, pull requests, and discussion since the last release!
<a href="https://github.com/brendad8" target="_blank" rel="noopener">@brendad8</a>, <a href="https://github.com/davidrsch" target="_blank" rel="noopener">@davidrsch</a>, <a href="https://github.com/dnldelarosa" target="_blank" rel="noopener">@dnldelarosa</a>, <a href="https://github.com/EmilHvitfeldt" target="_blank" rel="noopener">@EmilHvitfeldt</a>, <a href="https://github.com/jeroenjanssens" target="_blank" rel="noopener">@jeroenjanssens</a>, <a href="https://github.com/kbodwin" target="_blank" rel="noopener">@kbodwin</a>, <a href="https://github.com/lgaborini" target="_blank" rel="noopener">@lgaborini</a>, <a href="https://github.com/topepo" target="_blank" rel="noopener">@topepo</a>, and <a href="https://github.com/Wander03" target="_blank" rel="noopener">@Wander03</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-15_tidyclust-0-3-0/thumbnail.jpg" length="233780" type="image/jpeg" />
    </item>
    <item>
      <title>A brief and biased history of Posit data science agents</title>
      <link>https://opensource.posit.co/blog/2026-06-11_history-of-posit-data-science-agents/</link>
      <pubDate>Thu, 11 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-11_history-of-posit-data-science-agents/</guid>
      <dc:creator>Joe Cheng</dc:creator><description><![CDATA[<p>For the last 18 months, many of us at Posit have been focused on building AI agents designed to help RStudio and Positron users get their work done faster and more effectively. And we truly believe that we&rsquo;ve succeeded! Our latest agent, <a href="https://posit-dev.github.io/assistant/" target="_blank" rel="noopener">Posit Assistant</a>, is extremely good and getting better with each passing week.</p>
<p>But Posit Assistant was not our first agent, and it&rsquo;s not likely to be our last. The proliferation of agents coming from Posit has started causing serious confusion. This post hopes to clear that up by providing a chronology of the agents we&rsquo;ve created, our rationale for their design and architecture, and what you need to know about them today (if anything).</p>
<h2 id="tldr">TL;DR
</h2>
<p>Posit Assistant is our newest, most powerful agent for data science. It builds on what we learned from creating our previous agents: Positron Assistant and Databot.</p>
<table>
  <thead>
      <tr>
          <th>Name</th>
          <th>Description</th>
          <th>Status</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong><a href="https://posit-dev.github.io/assistant/" target="_blank" rel="noopener">Posit Assistant</a></strong></td>
          <td>Coding agent available in RStudio and Positron (and more)</td>
          <td>Active</td>
      </tr>
      <tr>
          <td><strong><a href="https://positron.posit.co/assistant.html" target="_blank" rel="noopener">Positron Assistant</a></strong></td>
          <td>Positron&rsquo;s coding agent</td>
          <td>Superseded by Posit Assistant, still available until Q3 2026</td>
      </tr>
      <tr>
          <td><strong><a href="https://positron.posit.co/databot.html" target="_blank" rel="noopener">Databot</a></strong></td>
          <td>EDA agent in Positron</td>
          <td>Superseded by Posit Assistant</td>
      </tr>
  </tbody>
</table>
<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">What about Posit AI?</span>
</div>
<div class="callout-body">
<p><a href="https://posit.ai/" target="_blank" rel="noopener">Posit AI</a>, not to be confused with Posit Assistant or Positron Assistant, is a model subscription service that can be used to power Posit Assistant.</p>
</div>
</div>
<h2 id="positron-assistant">Positron Assistant
</h2>
<ul>
<li><strong>Strengths:</strong> Time to market; connects to a variety of providers; tight IDE integration</li>
<li><strong>Weaknesses:</strong> Legacy agent architecture; not available in RStudio IDE</li>
<li><strong>Status:</strong> Succeeded by Posit Assistant (as of June 2026)</li>
</ul>
<p>This was the first &ldquo;off the shelf&rdquo; data science agent we ever shipped, and is available exclusively in Positron. It was forked from GitHub Copilot, and like Copilot, you can ask it general coding questions, have it write code, and have it explain existing code to you.</p>
<p>While the <a href="https://positron.posit.co/assistant.html" target="_blank" rel="noopener">Positron Assistant</a> experience is largely the same as Copilot, there are two major differences. The first is that it can access Positron-specific features like running code in an interactive R or Python session, inspecting variables, and generating/inspecting plots. These features make it much more useful for data work.</p>
<p>The second major difference is the ability to connect to other LLM providers than the Copilot service, right out of the box. At this time, Positron Assistant can connect to Anthropic and OpenAI (with API key), AWS Bedrock, Snowflake Cortex, GitHub Copilot, Microsoft Foundry, Google Gemini, <a href="https://posit.ai/" target="_blank" rel="noopener">Posit AI</a>, and even custom (OpenAI-compatible) providers. When running in a properly configured Posit Workbench installation, these can often be seamlessly authenticated.</p>
<p>GitHub Copilot is tightly integrated with VS Code, and Positron Assistant is similarly well integrated with Positron. You can start an &ldquo;inline chat&rdquo; right at your editor cursor and ask Positron Assistant to fix or explain errors with one click. These were major benefits to starting with GitHub Copilot as a base.</p>
<p>Unfortunately, there are serious downsides to the GitHub Copilot heritage as well. The architecture and core APIs of Copilot were designed to be extended <a href="https://code.visualstudio.com/api/extension-guides/ai/chat" target="_blank" rel="noopener">in ways</a> that didn&rsquo;t end up being very popular, while tool calling felt grafted on and cache control nonexistent. The net effect was a lot of abstraction and complexity between the user prompt and what actually went into the LLM request, making it difficult for us to get the best performance out of the LLM. And users noticed this, saying that Positron Assistant was generally effective but also duller than other agents, including Databot and Claude Code, even when using the same underlying model.</p>
<p>Another big downside to GitHub Copilot was that its chat UI was more austere and harder to customize than what we could do on our own. Any changes we made were asking for technical debt, multiplied by the incredibly fast pace of upstream changes that we need to regularly merge.</p>
<p>Overall, the Positron Assistant project gave us what we needed at the time: a competitive, full-featured, IDE-integrated, agentic coding assistant. But for data work, we felt like we knew how to do better in a number of ways, including agent performance and UI/UX.</p>
<h2 id="databot">Databot
</h2>
<ul>
<li><strong>Strengths:</strong> Effective agent harness; well tailored to exploration tasks</li>
<li><strong>Weaknesses:</strong> Not general purpose enough; not available in RStudio IDE</li>
<li><strong>Status:</strong> Succeeded by Posit Assistant (as of June 2026)</li>
</ul>
<p>Databot was conceived in December 2024, in response to a specific question: what would happen if we took a frontier model (Claude 3.5 Sonnet), seeded it with simple instructions, and gave it unfettered access to the most powerful tool we could think of (a live R or Python session)?</p>
<p>At the time, it seemed hopelessly reckless to give so much power to an LLM, and we ran the first prototype with real trepidation! Fortunately, this approach is more effective and significantly less dangerous in practice than in theory. In a world before Claude Code, Databot felt like absolute magic. That magic was compounded by three decisions we made in an effort to make Databot excellent for exploratory data analysis (EDA):</p>
<ol>
<li>The ability not only to execute code but to see the results (including tables and plots)</li>
<li>Keeping the human in the feedback loop by only allowing so many tool calls before stopping to summarize progress and check in with the user</li>
<li>Always providing three to five suggestions of what to do next</li>
</ol>
<p>With a suitably capable LLM, this combination makes data exploration feel like flying.</p>
<p>It took us months to convince ourselves that a tool like this was safe enough to ship to users, and months more to turn the prototype into production-ready software. Since this felt like such an aggressive architecture, we constrained Databot to a narrow remit: it was only for interactively exploring data. After exploration was complete, it could also export a reproducible Quarto report that records both the findings and the methods, but that was pretty much it.</p>
<p>We officially launched Databot on August 28, 2025 with <a href="https://posit.co/blog/introducing-databot" target="_blank" rel="noopener">this blog post</a>. We were still so nervous about the dangers that we published a <a href="https://posit.co/blog/databot-is-not-a-flotation-device" target="_blank" rel="noopener">companion blog post</a> at the same time, telling users how <em>not</em> to use Databot. As models have gotten better, and tens of millions of users have grown accustomed to aggressively agentic AI tools, this second blog post remains technically accurate but perhaps reads a bit alarmist. In practice, agentic AI coding tools are not completely safe, but neither are they necessarily as dangerous as they may theoretically appear. Their usefulness across a wide variety of tasks helps balance this risk.</p>
<p>Databot&rsquo;s crucial weakness turned out not to be that it was too dangerous, but too narrowly scoped. EDA does not happen in a vacuum. It&rsquo;s often done in service of some larger project, and insights derived from EDA typically immediately lead to the creation of a reproducible cleaning script, interactive Shiny app, automated data transformation job, etc. As an EDA-focused agent harness, Databot did not have the tools needed to do those tasks well.</p>
<p>This constrained nature left users feeling frustrated. Databot could be so intelligent, so facile with data exploration, yet so suddenly clumsy the moment you asked it to edit a data cleaning script, and the distinction felt arbitrary. Even if you understood Databot&rsquo;s limitations, it still left you needing to manually perform a &ldquo;hand off&rdquo; between an EDA session in Databot and a Shiny app authoring session in Positron Assistant.</p>
<h3 id="why-did-we-make-two-agents-instead-of-one">Why did we make two agents instead of one?
</h3>
<p>Given the obvious downsides of creating two distinct agents for Positron instead of focusing on one — confusion, disjointed experience, duplication of effort — why did we do it?</p>
<p>First, as stated above, Databot felt not just risky but borderline reckless in how much power we were encouraging the agent to wield. Positron Assistant had very similar behavior to Copilot, so while not totally &ldquo;safe&rdquo;, it felt like a widely understood and accepted set of tradeoffs. It felt reasonable to have one conservative agent that we could tell everyone to use in Positron Assistant, and one aggressive agent for early adopters to play with in Databot.</p>
<p>Second, Positron Assistant seemed easier to build because we could start with so much existing functionality from Copilot. For Databot, we had to build everything from scratch: the agent harness, the chat UI, loading and saving past conversations, everything, and it was hard to know in the beginning how fast we could do it and how successful we would be. Again, it felt right to have one agent that we knew we could ship quickly, and one that might take longer but would potentially give us a better long-term platform.</p>
<p>These reasons made sense to us in early 2025, but they haven&rsquo;t stood the test of time. As Databot gestated, Claude Code showed the world that powerful AI agents had a usefulness-to-risk ratio that far, far exceeded most people&rsquo;s thresholds. And forking Copilot didn&rsquo;t give us nearly the boost in development speed we expected: while it did give Positron Assistant a huge head start, it also saddled Positron Assistant with many unnecessarily complex abstractions and introduced us to the very expensive recurring task of merging the torrent of upstream Copilot changes with our extensive modifications.</p>
<p>A final reason had to do not with risk but with specialization: Databot&rsquo;s <a href="https://posit.co/blog/introducing-databot" target="_blank" rel="noopener">WEAR loop</a> felt distinct from Positron Assistant&rsquo;s generic loop, and this felt like a pretty fundamental UX difference. Once we got serious about combining the two, it turned out not to be so fundamental after all, and for the most part we feel like our next agent succeeds at &ldquo;code-switching&rdquo; as needed.</p>
<h2 id="posit-assistant">Posit Assistant
</h2>
<ul>
<li><strong>Strengths:</strong> Effective agent harness; great for EDA, coding, and general-purpose agent tasks; integrated into both Positron and RStudio</li>
<li><strong>Weaknesses:</strong> Not designed for highly autonomous or highly parallel agentic work (if that&rsquo;s what you&rsquo;re into)</li>
<li><strong>Status:</strong> Now available in RStudio, in preview in Positron</li>
</ul>
<p>Posit Assistant is our newest data science agent, designed to build on what we liked most about each of the AI agents we&rsquo;ve built and used in the past:</p>
<ul>
<li>A simple agent harness with a small number of powerful and general tools</li>
<li>Prompting, tools, and UI/UX designed for the needs of data scientists</li>
<li>Full access to live R/Python sessions, including variables and plots</li>
<li>Integrated into our data science IDEs</li>
<li>A codebase designed for fast iteration</li>
<li>Extension points based on MCP and skills</li>
</ul>
<p>Crucially, we built Posit Assistant to live in both RStudio (since April 2026) and Positron (since June 2026). Not only did this finally bring an integrated agent to RStudio, where most R users still live today, but it also meant that every ounce of effort we put into improving Posit Assistant helps both RStudio and Positron users.</p>
<p>While Positron Assistant (the older one) grew out of Copilot, Posit Assistant (the new one) grew out of Databot. There was much we liked about Databot, and it didn&rsquo;t take much effort to expand its mission from the narrow task of exploratory data analysis to being a great general-purpose agent.</p>
<p>The big advantage of Positron Assistant in early 2025 turned out to be a big disadvantage by early 2026: being built on GitHub Copilot. While Copilot was arguably one of the two or three best coding agents for most of its existence, by the end of 2025 it was thoroughly outmatched by Claude Code.</p>
<p>At the risk of oversimplifying, the Copilot approach thinks the user should play a major role in deciding what context the underlying LLM gets to see. For example, Copilot and Positron Assistant have UI features that let you specify what files from your project you want to include with your next user prompt. You can also use <code>@-mentions</code> to bring in reference materials (for example, the Shiny VS Code extension lets you mention <code>@shiny</code> to bring in the Shiny docs).</p>
<p>In contrast, Claude Code&rsquo;s approach is to give an LLM access to powerful general-purpose abilities, like reading/writing/editing files and executing arbitrary bash commands, and then trusting the LLM to use them to gather the information it needs. This approach worked well when Claude Code was released in February 2025, and then dramatically improved over the course of 2025. The overall effect was that Claude Code felt significantly smarter and more engaged, even when using the same underlying language model.</p>
<p><strong>The highest compliment I can give to Posit Assistant is this: when exploring data, it feels a lot like Databot, and when coding, it feels a lot like Claude Code.</strong> And where Claude Code is a general-purpose coding agent, Posit Assistant is built specifically for people who work with data, with modes and skills for data tasks like data cleaning, Shiny app creation, and predictive modeling.</p>
<h3 id="why-did-we-call-it-posit-assistant-when-we-already-had-positron-assistant">Why did we call it &ldquo;Posit Assistant&rdquo; when we already had &ldquo;Positron Assistant&rdquo;!?
</h3>
<p>Maybe the most controversial thing about Posit Assistant is its name: Posit Assistant. First, it&rsquo;s quite dull. Second, why isn&rsquo;t it &ldquo;Posit Agent&rdquo;? Third, why would you give it almost-but-not-exactly the same name as the thing it&rsquo;s superseding?</p>
<p>These are all decisions that were made by me, Joe Cheng, and if you think they are bad decisions, then just know the Posit Assistant team agrees with you. (I stand by the name, but only just!)</p>
<p><strong>On dullness:</strong> A natively integrated AI chatbot for an IDE is table stakes, not a distinguishing feature. While we need a name today, I want users to eventually think of Posit Assistant as &ldquo;RStudio&rsquo;s chat pane&rdquo; or &ldquo;Positron&rsquo;s built-in AI&rdquo;, that is, for its identity to be subsumed into the host IDE. We believe most data practitioners will continue to use IDEs even as these models get more capable, and that the &ldquo;I&rdquo; in IDE is where we can offer real value. Instead of trying to build a new, distinct brand, I wanted there to barely be a brand at all.</p>
<p><strong>On &ldquo;Agent&rdquo;:</strong> The term &ldquo;agent&rdquo; is heavily overloaded these days. In the sense that it&rsquo;s been used so far in this article, it means <a href="https://simonwillison.net/2025/May/22/tools-in-a-loop/" target="_blank" rel="noopener">&ldquo;models using tools in a loop,&rdquo;</a> and Posit Assistant definitely fits that definition. But a lot of our customers understand the word to refer to &ldquo;models working highly autonomously for long periods of time&rdquo;, and we really wanted to avoid this connotation. Posit Assistant does have the ability to work autonomously if necessary and appropriate, but much of our harness encourages the model not to work autonomously in situations where it otherwise would.</p>
<p><strong>On Positron Assistant/Posit Assistant:</strong> This one is truly confusing, and the hardest one to defend. My defense is that the confusion is temporary. For most of Positron Assistant&rsquo;s short history, Posit Assistant didn&rsquo;t exist. For most of Posit Assistant&rsquo;s history, Positron Assistant will no longer exist. We&rsquo;re in a moment right now where we&rsquo;re mid-transition, so it&rsquo;s very confusing, but hopefully, that confusion is temporary.</p>
<p>I hope you will find this explanation to be satisfactory, and if not, at least now you know who to blame!</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-11_history-of-posit-data-science-agents/images/featured.png" length="248470" type="image/png" />
    </item>
  </channel>
</rss>
