30  🏗 btw

Published

2026-04-09

CautionAlert

The contents for section are being revised. Thank you for your patience.

In the previous chapters, we used the chores package to develop helpers—pre-written prompts—for repetitive tasks such as creating roxygen2 documentation and updating testthat tests.1 In the gander chapter, we used the addin to send code and environment context to the LLM.2

chores and gander assist us in automating or enhancing the prompts we submit to the LLM. The addins and shortcuts streamline interactions with the model, providing an experience that resembles a browser interface (as opposed to chatting with live_console(chat) or live_browser()).

I’ve created the shinypak R package in an effort to make each section accessible and easy to follow. Install shinypak using pak (or remotes):

install.packages('pak')
pak::pak('mjfrigaard/shinypak')
library(shinypak)

List the apps in this chapter:

list_apps(regex = '^30')

Launch apps with launch()

launch(app = '30_llm-btw')

Download apps with get_app()

get_app(app = '30_llm-btw')

In this chapter we’ll focus on the btw package, which is fundamentally different than chores and gander because it,

provides a default set of tools to to peruse the documentation of packages you have installed, check out the objects in your global environment, and retrieve metadata about your session and platform.3

I’ll cover using btw to add documentation and improve the contents of the downloadable report in our sap application.

30.1 Configuration

Install ellmer and btw:

install.packages('ellmer')
# or the dev version
pak::pak('tidyverse/ellmer')
install.packages("btw")
# or the dev version
# install.packages("pak")
pak::pak("posit-dev/btw")

We can place the btw configuration options in the .Rprofile (similar to other ellmer configurations).4 Recall that the .Rprofile file can exist at the user and/or the project-level.

For example, I’ve added a project-level .Rprofile file this branch of the sap package and included a system_prompt and model:

if (interactive()) {
  require(ellmer, quietly = TRUE)
}
if (interactive()) {
  require(btw, quietly = TRUE)
}
options(
  btw.client = ellmer::chat_anthropic(
    system_prompt =
    "You are an expert R/Python programmer who loves explaining complex topics to non-technical audiences. 
    ...",
    model = "claude-sonnet-4-5-20250929"
    )
)
1
Ensure ellmer package
2
Ensure btw package
3
btw config
4
System prompt for all conversations with chat
5
model argument for most current Claude model

I’ve included the full system prompt below in an easier to read format:

The instructions for returning Markdown/R Markdown/Quarto responses are important for this particular app, as you’ll see below.

You are an expert R/Python programmer who loves explaining complex topics to non-technical audiences.

  • When writing R code, use base R functions, but follow the tidyverse style guide.

  • Avoid using for loops and prefer functional programming patterns like apply() or purrr.

  • When creating graphs/plots, use ggplot2. - When returning bash/shell commands, give each command their own code chunk (see example below):

DON’T DO THIS:

# Check the user's shell
getent passwd abc1

# Check SSH daemon logs for connection details
sudo tail -50 /var/log/secure | grep abc1

# Check if rsync is installed and accessible
which rsync

DO THIS:

Check the user’s shell:

getent passwd abc1

Check SSH daemon logs for connection details:

sudo tail -50 /var/log/secure | grep abc1

Check if rsync is installed and accessible:

which rsync
  • If your response includes markdown, RMarkdown, or Quarto syntax, wrap the contents in tilde blocks (~~~). See the example below:

---
title: 'Report'
output: html_document
---

```/{r setup, include=FALSE/}
knitr::opts_chunk$set(echo = TRUE)
install.packages('tidyverse')
```

## Analysis

Example analysis...

```/{r analysis/}
library(tidyverse)
# ...
```
  • If writing R Shiny code, use bslib for all layout functions (unless explicitly instructed otherwise).
  • If writing Python Shiny code, use shiny core (not express) to build apps and include explanations in comments.

Launch app with the shinypak package:

launch('30_llm-btw')

30.2 btw tools

btw comes with a set of pre-defined tools for examining package documentation, environments, directories and files, git and GitHub, our development environment, CRAN packages, R sessions, and general web searches.5

We can use btw to work interactively in RStudio console by creating a btw-enhanced chat client and passing messages directly.6

chat <- btw_client()

We can view the btw enhancements by examining the chat object in the console:

chat

btw has added ‘System and Session Context’ and ‘Tools’ sections to the chat client.

<Chat Anthropic/claude-sonnet-4-5-20250929 turns=1 input=0 output=0 cost=$0.00>
── system ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
# System and Session Context

Please account for the following R session and system settings in all responses.

<system_info>
R_VERSION: R version 4.5.2 (2025-10-31)
OS: macOS Tahoe 26.4
SYSTEM: x86_64, darwin20
UI: RStudio
LANGUAGE: (EN)
LOCALE: en_US.UTF-8
ENCODING: en_US.UTF-8
TIMEZONE: America/Phoenix
DATE: Thursday, April  2, 2026 (2026-04-02)
RSTUDIO: 2026.04.0-daily+381 Globemaster Allium (desktop)
</system_info>


# Tools

You have access to tools that help you interact with the user's R session and workspace. Use these tools when they are helpful and appropriate to complete the user's request. These tools are available to augment your ability to help the user, but you are smart and capable and can answer many things on your own. It is okay to answer the user without relying on these tools.


## Skills

You have access to specialized skills that provide detailed guidance for specific tasks. Skills are loaded on-demand to provide domain-specific expertise without consuming context until needed.

### Using Skills

1. **Check available skills**: Review the `<available_skills>` listing below
2. **Load when relevant**: When you recognize that a task matches a skill's description, call `btw_tool_skill(name)` to load the full skill instructions
3. **Don't reload**: If a skill has already been loaded in this conversation, follow its instructions directly without loading it again
4. **Access resources**: After loading, use file read tools to access references

Skills may include bundled resources:
- **Scripts**: Code bundled with the skill. Scripts are not directly executable by btw; read them for reference or adapt their logic into R code for use with the R code execution tool.
- **References**: Additional documentation to consult as needed
- **Assets**: Templates and files for use in outputs

<available_skills>
<skill>
<name>skill-creator</name>
<description>Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.</description>
<location>/Users/mjfrigaard/Library/Caches/org.R-project.R/R/renv/cache/v5/macos/R-4.5/x86_64-apple-darwin20/btw/1.2.1/9d2abfeb6859f7d079b14da1f8d84ad8/btw/skills/skill-creator/SKILL.md</location>
</skill>
</available_skills>

We can see the model will have some additional instructions and methods at it’s disposal (beyond what we’ve provided in the system_prompt argument of ellmer::chat_anthropic()).

All btw functions follow the name convention below:

Prefix Group Name
btw_tool_ files_ code_search()
btw_tool_ files_ list_files()
btw_tool_ files_ read_text_file()
btw_tool_ files_ write_text_file()

The Group can be one of docs, env, git, github, ide, search, session or web. This is important to remember below in Section 30.8

30.3 The btw.md context file

As we learned in the ellmer chapter, project prompts should be stored in inst/prompts/. btw also provides the use_btw_md() function, which creates a project ‘context file.’

use_btw_md(scope = "project")
ℹ See btw::btw_client for format details
ℹ See btw::btw_tools for available tools
ℹ Call `btw::btw_task_create_btw_md()` to use an LLM to help you initialize the project context.

The btw.md file comes with some default content for project context:

Use btw.md to inform the LLM of your preferred code style, to provide domain-specific terminology or definitions, to establish project documentation, goals and constraints, to include reference materials such or technical specifications, or more. Storing this kind of information in btw.md may help you avoid repeating yourself and can be used to maintain coherence across many chat sessions.

client

The YAML header in our newly created btw.md is where can specify the client (along with the provider and model).

---
client: claude/claude-sonnet-4-5-20250929
---

The default values in btw.md will automatically use the latest Claude model from Anthropic. The YAML values above are similar to using ellmer’s chat_* functions.7

tools

The tools section of the YAML header contains a list of the groups from btw_tools(). Each of these groups contains a collection of functions “that allow the chat to interface with your computational environment.

---
tools:
  - docs
  - env
  - files
  - git
  - github
  - ide
  - search
  - session
  - web
---

Additional instructions are also provided on code style:

Follow these important style rules when writing R code:

* Prefer solutions that use {tidyverse}
* Always use `<-` for assignment
* Always use the native base-R pipe `|>` for piped expressions

Now that we have a btw.md file for project context, we’ll start an interactive chat session to help us write our context file.

30.4 Console chat

We can use the btw tools to add more project context to the btw.md by calling btw_task_create_btw_md() and setting the mode to "console".

btw_task_create_btw_md(mode = "console", client = "anthropic")

This opens an interactive chat in the console:

30.5 How tool calling works

LLMs can’t execute R code, but if we’ve registered tools (i.e., R functions) with the model, they can be used to help provide additional information.

As mentioned above, btw has a collection of tools at our disposal, so when we start the interactive chat, the model informs us it will be examining the sap package contents for more information.

>>> "Let's get started."

The model tells us it’s intentions for the btw.md file:

I'll begin by exploring the project structure to understand 
what we're working with.

To perform this exploration, the model will use the registered tools from btw.

30.5.1 Registered tools

When registering a tool with an LLM, we include the name of the function, a description of what the function does, and a list of function arguments with their type (boolean, integer, number, etc.). 8

We can view all the tools registered with the LLM using:

chat$get_tools()

The btw_tools() function lists all the tools we could register, but chat$get_tools() returns the tools that are registered with the model.

The output from chat$get_tools() is rather lengthy, but we can use Ctrl/Cmd + F to locate btw_tool_files_list(), which is the first tool called by the LLM.

$btw_tool_files_list
# <ellmer::ToolDef> btw_tool_files_list(path, type, regexp, `_intent`)
# @name: btw_tool_files_list
# @description: List files or directories in the project.

WHEN TO USE:
* Use this tool to discover the file structure of a project.
* When you want to understand the project structure, use `type = "directory"` 
  to list all directories.
* When you want to find a specific file, use `type = "file"` and `regexp` to
  filter files by name or extension.

CAUTION: Do not list all files in a project, instead prefer listing files in
a specific directory with a `regexp` to filter to files of interest.

View the entire output from chat$get_tools() in inst/prompts/btw-chat-get-tools.md.

30.5.2 Tool calls

Recall the model was going to start by exploring your project structure, and we can see this aligns with the’ WHEN TO USE section of btw_tool_files_list().

WHEN TO USE:

  • Use this tool to discover the file structure of a project.

  • When you want to understand the project structure, use type = "directory" to list all directories.

  • When you want to find a specific file, use type = "file" and regexp to filter files by name or extension.

In the console, the tool call and a preview of it’s results are printed. Below is the first call to btw_tool_files_list():

◯ [tool call] btw_tool_files_list(path = ".", 
`_intent` = "List root directory to identify project type and structure")

The function includes an _intent argument, where the model, “explain[s] why it called the tool.9

30.5.3 Tool results

The results are a markdown formatted table of the project contents (file/folder names, their size, and when they were last changed):10

● #> | path        | type      | size  | modification_time   |
  #> |-------------|-----------|-------|---------------------|
  #> | DESCRIPTION | file      | 533   | 2026-04-01 08:47:33 |
  #> | NAMESPACE   | file      | 977   | 2026-04-01 08:47:33 |
  #> | R           | directory | 1.03K | 2026-04-01 08:47:33 |
  #> …

30.5.4 Model response

The table output is sent to the LLM, which them provides a summary of the contents:

Great! This is clearly an **R package** for building a Shiny application. 

I’ve created an overview of this tool call in the diagram below.11

Tool calls with btw

Tool calls with btw

The model is equipped with the supplemented system prompt–which informs it of the tools at it’s disposal–and when we begin, it starts by attempting to understand the project structure.

This objective matches the btw registered tool description for the btw_tool_files_list() function, which returns a markdown-formatted table of the project contents. The results from the tool are sent to the LLM, which provides a summary in the console.

The entire PHASE 1: PROJECT EXPLORATION is in the callout box below (expand the callout box to view).

Let me read the critical files now.

◯ [tool call] btw_tool_files_read(path = "DESCRIPTION", 
`_intent` = "Read DESCRIPTION to understand package metadata
and dependencies")
● #> 1:2b2|Package: sap
  #> 2:917|Title: btw movies app
  #> 3:a71|Version: 0.0.0.1000
  #> 4:778|Author: John Smith <John.Smith@email.io> [aut, cre]
  #> 5:913|Maintainer: John Smith <John.Smith@email.io>
  #> …
◯ [tool call] btw_tool_files_read(path = "README.md", 
`_intent` = "Read README to understand the project purpose")
● #> 1:432|<h1 align="center"> <code><strong>sap</strong></code> </h1>
  #> 2:4b0|<h3 align="center"> Code examples for <a href="https://mjfrigaard.github.io/shiny-app-pkgs/"> Shiny App-Packages
</a> </h3>
  #> 3:2a0|<hr>
  #> 4:bf8|
  #> 5:2a0|<hr>
  #> …
◯ [tool call] btw_tool_files_read(path = "btw.md", 
`_intent` = "Read existing btw.md to see what documentation already exists")
● #> 1:bb2|---
  #> 2:d0f|client: claude/claude-sonnet-4-5-20250929
  #> 3:a58|tools:
  #> 4:bed| - docs
  #> 5:118| - env
  #> …
◯ [tool call] btw_tool_files_read(path = "app.R", 
`_intent` = "Read app.R to understand the application entry point")
● #> 1:9f9|# set option to turn off loadSupport() ----
  #> 2:1fe|withr::with_options(new = list(shiny.autoload.r = FALSE), code = {
  #> 3:8c2| if (!interactive()) {
  #> 4:e06| sink(stderr(), type = "output")
  #> 5:7a9| tryCatch(
  #> …

Good! Now let me read some key R files to understand the structure better.

◯ [tool call] btw_tool_files_read(path = "R/nav_ui.R", 
`_intent` = "Read nav_ui.R to understand the UI structure and
navigation")
● #> 1:4cf|#' User Interface for the Shiny Application
  #> 2:00e|#'
  #> 3:4e0|#' This function defines the user interface for the Shiny application,
  #> 4:94a|#' integrating Bootstrap and a custom JavaScript file.
  #> 5:00e|#'
  #> …
◯ [tool call] btw_tool_files_read(path = "R/nav_server.R", 
`_intent` = "Read nav_server.R to understand server logic")
● #> 1:5bc|#' Application Navigation Server Logic
  #> 2:00e|#'
  #> 3:ecb|#' @param input,output Shiny server input and output objects.
  #> 4:00e|#'
  #> 5:e7f|#' @return Server logic for the Shiny application with navigation.
  #> …
◯ [tool call] btw_tool_files_read(path = "R/launch_app.R", 
`_intent` = "Read launch_app.R to understand the application launch mechanism")
● #> 1:188|#' Launch the Shiny Application
  #> 2:00e|#'
  #> 3:d51|#' This function initializes and launches the Shiny application with specified
  #> 4:d2a|#' options and settings.
  #> 5:00e|#'
  #> …
◯ [tool call] btw_tool_files_read(path = "R/data.R", 
`_intent` = "Read data.R to understand the included datasets")
● #> 1:a59|#' @title IMDB movies data
  #> 2:00e|#'
  #> 3:760|#' @description
  #> 4:c89|#' Movie review data. Note: these data come from the [Building Web Applications with shiny
course](https://rstudio-education.github.io/shiny-course/).
  #> 5:00e|#'
  #> …

Excellent! Now let me check one of the module files and look at a vignette to understand the structure better.

◯ [tool call] btw_tool_files_read(path = "R/mod_compare_vars.R", 
`_intent` = "Read one module to understand the Shiny module pattern used")
● #> 1:fa7|#' UI for variable comparison module
  #> 2:00e|#'
  #> 3:ccf|#' Creates inputs for selecting variables to compare in a scatter plot with
  #> 4:9c8|#' customizable appearance options. This function is designed to work together
  #> 5:1d0|#' with [mod_compare_vars_server()].
  #> …
◯ [tool call] btw_tool_docs_available_vignettes(package_name = "sap", 
`_intent` = "List available vignettes to understand documentation structure")
■ #> Error: Package sap has no vignettes.

Let me check the vignettes directory directly and also look at the NAMESPACE to understand exports.

◯ [tool call] btw_tool_files_read(path = "NAMESPACE", 
`_intent` = "Read NAMESPACE to see exported functions")
● #> 1:074|# Generated by roxygen2: do not edit by hand
  #> 2:bf8|
  #> 3:6fe|export(compare_plot)
  #> 4:dd1|export(count_bar_plot)
  #> 5:d5e|export(desym)
  #> …
◯ [tool call] btw_tool_files_read(path = "vignettes/structure.qmd", 
line_start = 1L, ...)
● #> 1:bb2|---
  #> 2:032|title: "Structure"
  #> 3:965|format:
  #> 4:7d1| html:
  #> 5:235| toc: true
  #> …

Perfect! I have a good understanding now. Let me read just a bit more of the structure vignette to see the explanation and then check one test file.

◯ [tool call] btw_tool_files_read(path = "vignettes/structure.qmd", 
line_start = 100L, ...)
● #> 100:323|│ ├── mod_counts_tbl_ui.Rd
  #> 101:2cc|│ ├── mod_counts_vars_server.Rd
  #> 102:ab0|│ ├── mod_counts_vars_ui.Rd
  #> 103:355|│ ├── mod_counts_vbox_server.Rd
  #> 104:5a7|│ ├── mod_counts_vbox_ui.Rd
  #> …
◯ [tool call] btw_tool_files_read(
  path = "tests/testthat/test-mod_compare_vars_server.R", 
...)
● #> 1:522|# Define a test for the compare_vars module
  #> 2:10b|describe(
  #> 3:dc7| "Feature: Variable Selection Prevention of Duplicate X/Y Variables
  #> 4:799| As a user
  #> 5:16f| I want to avoid selecting the same variable for both X and Y axes
  #> …

Perfect! I have a solid understanding of the project. Let me now summarize what I’ve learned.

As we can see, the model called btw_tool_files_read() and btw_tool_docs_available_vignettes() to gather context on the project.

The PPHASE 1 COMPLETE - Summary of Findings is what the model learned about the sap package using the btw tools (expand the callout box to view):

Project Type and Technology

This is an R Package that contains a Shiny web application for exploring movie review data.

Key Findings

Purpose:

  • Interactive data visualization app for exploring IMDB and Rotten Tomatoes movie review data

  • Demonstrates best practices for building Shiny applications as R packages (“Shiny App-Package”)

  • Created as a companion example for the “Shiny App-Packages” book

Technology Stack:

  • Framework: R Shiny with bslib for modern Bootstrap UI

  • Core Dependencies: dplyr, ggplot2, tidyr, forcats (tidyverse ecosystem)

  • UI Theming: bslib with dark theme, thematic for ggplot2 styling

  • Testing: testthat with shiny::testServer() for module testing

  • Documentation: Quarto vignettes (not built into package but source files exist)

  • Environment: renv for reproducible dependency management

Architecture:

  • Modular Structure: Follows Shiny module pattern with UI/server separation

  • Module Categories:

    • Compare modules: Scatter plots for comparing numeric variables

    • Counts modules: Bar charts, waffle charts, and tables for categorical counts

    • Distributions modules: Box plots and raincloud plots for distributions

    • Awards modules: Visualizations for award recipients

    • Report module: Download functionality for reports

  • Navigation: Main app uses nav_ui() and nav_server() with bslib::page_navbar()

  • Launch: launch_app() entry point with display_type() helper for RStudio integration

Data:

  • Primary dataset: movies

  • 651 IMDB/Rotten Tomatoes movie reviews with 34 variables

  • Color palettes: clr_pal, clr_pal3, clr_pal12 for consistent styling

  • Source: From “Building Web Applications with Shiny” course

Testing Approach:

  • Uses testthat with shiny::testServer() for testing module server logic

  • BDD-style test structure (describe/it pattern)

  • Tests focus on reactive behavior and input validation

Key Patterns:

  • Logging with custom logr_msg() function using logger package

  • Helper functions for variable name formatting (name_case())

  • Data extraction utilities (get_award_recipients(), dist_var_summary())

  • Reactive variable selection with auto-update logic to prevent duplicate axes

Directory Structure:

  • Standard R package layout

  • inst/ contains production app, static files, templates

  • data-raw/ for data preparation scripts

  • Comprehensive test coverage for all modules

After the project summary, the model starts PHASE 2: QUESTIONS FOR CONTEXT, which includes a series of questions from the LLM regarding the context of the package/project (expand the callout box to view).

Now moving to Phase 2 to gather context from the user.

Question 1 of 5

I can see this is a teaching example for the Shiny App-Packages book. Beyond demonstrating the app-package pattern, what should developers understand about the design philosophy or goals of this specific application?

For example: - Is this meant to showcase specific Shiny patterns or best practices? - Are there particular architectural choices (like the module organization) that are pedagogical? - Is there a balance between “production-ready” code and “teaching-friendly” simplicity?

This is meant to showcase Shiny best practices (modules) with a balance between “production-ready” code and “teaching-friendly” simplicity.

◯ [tool call] btw_tool_files_read(path = "R/mod_report.R", 
`_intent` = "Check the report module to understand the
download/reporting functionality")
● #> 1:d25|#' Report download UI
  #> 2:00e|#'
  #> 3:336|#' @param id Module ID
  #> 4:00e|#'
  #> 5:9f6|#' @return UI elements for downloading report
  #> …

Good! Now let me continue with the questions.

Question 2 of 5

I see the app has four main feature areas (Compare, Counts, Distributions, Awards) organized as module groups. Is there a reason these specific features were chosen, or are they meant to demonstrate different types of common data visualization patterns?

For example: - Compare = scatter plots / bivariate relationships - Counts = categorical summaries - Distributions = continuous variable summaries
- Awards = filtering/subsetting patterns

Or is it more about showcasing variety in the UI components (tables, plots, different chart types)?

The module names are meant to demonstrate different types of common data visualization patterns.

Question 3 of 5

I notice there’s extensive logging throughout the modules using a custom logr_msg() function. Is this logging approach meant to demonstrate production monitoring practices, or is it primarily pedagogical (showing developers where to think about observability in Shiny apps)?

Also, are there any non-obvious gotchas or conventions in the codebase that new contributors should know about? For example: - The variable name formatting with name_case() - The reactive value passing pattern between var modules and display modules - The use of desym() for cleaning reactive values before passing to R Markdown - The test organization or any testing patterns worth highlighting

The patterns in this app are merely meant to show developers where to think about observability in Shiny apps.

Question 4 of 5

I see the package uses renv for dependency management and has both a root-level app.R and inst/prod/app.R. Can you clarify the deployment/usage model?

Specifically: - When would developers use launch_app() vs. running app.R vs. the production app in inst/prod/? - Is the inst/prod/app.R setup meant to demonstrate deployment patterns (like publishing to shinyapps.io or Posit Connect)? - Are there specific workflow considerations for development vs. production that developers should understand?

launch_app() and app.R can be used dynamically for development, while the production app in inst/prod/ is intended for deployment to platforms like shinyapps.io or Posit Connect.

Question 5 of 5

Looking at the vignettes in the vignettes/ folder (structure.qmd, graphs.qmd, tables.qmd, specs.qmd, report.qmd), I noticed they’re Quarto files but the package uses VignetteBuilder: quarto in DESCRIPTION, yet they don’t seem to be built when I checked for vignettes.

Are these vignettes meant to be: - Built and browsable as part of the package documentation? - Maintained as external documentation/tutorials that developers should read in the source? - Part of a separate documentation website (like pkgdown)?

Also, is there anything else about this project’s purpose, structure, or design decisions that would be important for a developer to understand when first encountering this codebase?

The Quarto vignettes are intended to be built and browsable as part of the package documentation. Quarto HTML Vignettes are available in R packages (https://cran.r-project.org/web/packages/quarto/vignettes/hello.html). This project is intended to demonstrate examples/best practices and provide a clear structure for building a Shiny app within an R package.

Perfect! Thank you for those clarifications. Let me now present the narrative sections I’ll include in the btw.md file.

I included some instructions to the model on building mermaid diagrams for the btw.md file. The PHASE 2: NARRATIVE CONSTRUCTION returned after the questions and answers is below (expand the callout box to view).

Here’s the Project Context I’ve drafted:

sap is a teaching example demonstrating best practices for building Shiny applications as R packages. It provides an interactive data visualization interface for exploring IMDB and Rotten Tomatoes movie review data, created as a companion to the Shiny App-Packages book.

Does this look good to you?

Yes

Great! Here’s the Key Design Decisions section I’ve drafted:

Key Design Decisions

  • Modular architecture: Uses Shiny modules throughout to demonstrate scalable app organization with clear UI/server separation
  • Balance of simplicity and production patterns: Code is teaching-friendly while demonstrating real-world practices like logging, testing, and theming
  • Feature variety as pedagogy: Four module groups (Compare, Counts, Distributions, Awards) showcase common visualization patterns—bivariate scatter plots, categorical summaries, continuous distributions, and data filtering
  • Development vs. deployment paths: launch_app() and root app.R support interactive development; inst/prod/app.R demonstrates deployment to Posit Connect or shinyapps.io
  • Observability patterns: Logging with logr_msg() shows developers where to consider monitoring and debugging in production Shiny apps

Does this look good to you?

Yes

Excellent! Here’s the Developer Orientation section I’ve drafted:

Developer Orientation

  • Start with the module pattern: Read R/mod_compare_vars.R and R/mod_compare_point.R to understand the UI/server module structure used throughout
  • Navigation flow: R/nav_ui.R and R/nav_server.R tie all modules together—this is where module composition happens
  • Reactive value passing: Variable selection modules (mod_*_vars_server) return reactive lists consumed by display modules (mod_*_point_server, mod_*_bar_server, etc.)
  • Testing approach: Tests use shiny::testServer() with BDD-style structure (see tests/testthat/test-mod_compare_vars_server.R)
  • Helper utilities: name_case() formats variable names for display; desym() cleans reactive values before passing to R Markdown reports
  • Vignettes for depth: Quarto vignettes in vignettes/ provide detailed architectural documentation (structure.qmd is the starting point)

Does this look good to you?

Yes

Perfect! I now have all the context I need. Let me tell you what I’m ready to do:


30.6 READY TO WRITE

I’m ready to create the ./btw.md file with a comprehensive developer-focused summary that includes:

  1. Narrative sections (the ones you just approved):
    • Project Context
    • Key Design Decisions
    • Developer Orientation
  2. Technical reference sections:
    • Quick Reference (project type, language, key frameworks)
    • Architecture overview (module structure, launch mechanisms)
    • Data sets (the movies dataset dictionary)
    • Technical Details (dependencies, directory structure, key components)
    • Development Workflow (setup, common tasks, testing)
    • Important Notes (gotchas and conventions)
    • Resources (links to related documentation)

I noticed there’s already a btw.md file with YAML front matter and some style rules. Would you like me to:

A. Keep the existing front matter and style rules, integrating them into the new comprehensive documentation?

B. Replace everything with a fresh btw.md that just preserves the front matter block?

C. Something else?

What would you prefer?

Replace everything

30.7 Updated bwd.md file

When Phase 2 is complete, we’re told we have a “comprehensive btw.md file that captures the essential information about [our] Shiny App-Packages project.” The full conversation with the model is stored in inst/prompts/btw-md-conversation.md.

View the complete updated btw.md file on GitHub.

30.8 btw App

Now that we have a project context file, we will use btw to improve our downloadable report. Instead of using the console to chat with the model, we’ll use the btw app:12

btw_app()

If we expand the sidebar, we can see the registered tools provided by btw:

By default, the app includes all of the tools available from btw (with the exception of Subagent, Load skill, and Package Tools).13

30.8.1 Agents

If you’re new to LLMs, btw somewhat drops you in the deep end of the pool. As we can see from the App interface, we’re able to add a Subagent to the available tools.

Agents in btw_app()

Agents in btw_app()

What is the difference between an agent and a subagent?

Generally speaking, an Agent is an LLM chat session that uses tools autonomously to complete tasks. A Subagent is a specialized tool the Agent can delegate subtasks to—like a manager (Agent) assigning work to a specialist (Subagent).

%%{init: {'theme': 'neutral', 'themeVariables': { 'fontFamily': 'monospace', "fontSize":"16px"}}}%%

flowchart TD
    User(User) -->|prompt| Agt("Agent<br>(LLM + tools)")
    Agt -->|delegates subtask| SubAgt("Subagent<br>(tool-shaped agent)")
    SubAgt -->|result| Agt
    Agt -->|final response| User
    

Agents vs. subagents

The documentation for the subagent is available in the btw documentation,14 but at a high level, it’s instructed to “work efficiently; deliver complete, actionable answers; use available tools effectively; handle problems clearly; and build on previous work.”

30.8.2 Skills

Skills in btw_app()

Skills in btw_app()

30.8.3 Package Tools

30.8.4 Using the app

The btw_app() functions like any LLM chat interface.


  1. The prompt directory is where chores stores the helpers used in the addin.↩︎

  2. This is explained in What is gander actually doing?↩︎

  3. This description of btw actually comes from the mcptools documentation.↩︎

  4. You can easily open this file with usethis::edit_r_profile().↩︎

  5. Read a complete list of available tools from btw in the package documentation.↩︎

  6. An ellmer::Chat client which provides a “sequence of user and assistant Turns sent to a specific Provider.↩︎

  7. For more information on client values, read the Chat Settings documentation.↩︎

  8. We can register tools using ellmer’s create_tool_def() and tool() functions.↩︎

  9. The _intent argument is “An optional string describing the intent of the tool use. When the tool is used by an LLM, the model will use this argument to explain why it called the tool.↩︎

  10. I’ve cleaned up the formatting on this markdown table so it’s easier to read.↩︎

  11. This image was inspired by the tool calling overview presented in Garrick Aden-Buie’s genAI 2025: Using LLMs in Shiny presentation.↩︎

  12. To run the btw_app(), make sure you have the latest version of shinychat.↩︎

  13. Subagent uses SOURCE, Load skill uses SOURCE, Package Tools uses SOURCE↩︎

  14. See the btw-subagent.md file for the full set of instructions.↩︎