--- title: "Custom Agents" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Custom Agents} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} #| include: false library(btw) ``` As a conversation with an LLM gets longer, everything in it shapes the next response: the files it has read, its earlier answers, the direction the work has taken. That helps when you're building on earlier work. It gets in the way when you want an independent check of something, or when one job needs different tools or a different model than the rest of the conversation. You could open a new chat for each of those jobs, but then you'd rewrite the instructions every time and copy material between chats by hand. **Custom agents** let your chat run those jobs for you. A custom agent is a Markdown file that describes one job: its instructions, the tools it can use, and, optionally, the model it runs on. Your chat can hand off that job whenever it's needed, and the result comes back into the conversation. Because the files live in your project, you write each job's instructions once and reuse them. Behind the scenes, btw turns each agent file into a tool for your main `btw_app()` or `btw_client()` chat. When the main chat client calls the tool, it writes a prompt describing the task. btw then starts a *subagent*, which is a separate chat session configured by the agent file. The subagent sees only its own instructions, its tools, and that prompt. Its final response comes back to the main chat as the tool's result. In this vignette, we'll write three custom agents, make them available to a btw chat, and ask that chat to delegate work to them. Agent files use the same format as `btw.md`; if you haven't written a `btw.md` file before, start with `vignette("btw-md")`. To follow along, you'll need btw, an [ellmer](https://ellmer.tidyverse.org/) chat provider you can use, and [shinychat](https://posit-dev.github.io/shinychat/) if you want to run the chat app. ## An example: reviewing a report Our example borrows from the way people review each other's work. Suppose you've written a research report and want a second opinion before sharing it. You might ask three colleagues to read it: one to check where the data came from, another to reproduce the numerical claims, and a third to check whether the conclusions survive both reviews. We'll build a custom agent for each of those three jobs. The analogy only goes so far. The agents don't work together the way colleagues would. Each review is a separate call from the main chat to an agent's tool, and each call runs in its own session, so the sessions never see each other. Anything one agent needs from another's review has to be written into its prompt by the main chat. With custom agents, the main chat makes the calls and collects the results, and the agent files stay in your project for the next report. For this review, we'll create a `btw.md` file and three files in the project's `.btw/agents/` directory: ``` text btw.md .btw/agents/ researcher.md statistician.md fact_checker.md ``` Once those files exist, you can start up `btw_app()` and ask the main chat to delegate the review to all three agents and summarize what they found. ## Give the main chat access to agents Starting from the `btw.md` file in `vignette("btw-md")`, we make one change: add the `agent` group to `tools`. This file is saved at the project root (or your working directory). ``` text --- client: posit tools: [files, docs, sessioninfo, run, agent] --- You are a research data science assistant. Inspect the project files before answering. ``` When the `agent` tool group is included, btw adds one tool for each agent file btw finds. If you list `tools` in `btw.md` but leave out `agent`, the main chat won't receive those tools, even when the agent files exist. The other groups in `tools` here let the main chat inspect the report, consult documentation, and run R. They don't determine what an agent can use; each agent has its own tool list. From the project directory, launch the app with `btw_app()` or, for programmatic use, call `btw_client()`. ```{r} #| eval: false library(btw) btw_app() ``` ## Create an agent file An agent file starts out looking like any other `btw.md` file. Here's one for the statistician. Its frontmatter chooses tools for reading files, looking up documentation, and running R, and its body describes the job: ``` text --- tools: [files_read, docs, run] --- You are a consulting statistician. Read the report named in the task. Use R to check its sample size, model, and reported estimates. Separate what the analysis establishes from what the report concludes. Do not edit files. Return a memo with the data used, checks run, discrepancies, and recommended corrections. ``` If you saved this as the project's `btw.md`, every chat in the project would get the statistician's instructions and tools. To turn these instructions into a subagent, we need two more fields: `name` identifies the agent, and `description` tells the main chat when to use it. We'll also add an optional `title` for display. ``` text --- name: statistician title: Consulting statistician description: Reproduce and critique the numerical claims in a research report. tools: [files_read, docs, run] --- You are a consulting statistician. Read the report named in the task. Use R to check its sample size, model, and reported estimates. Separate what the analysis establishes from what the report concludes. Do not edit files. Return a memo with the data used, checks run, discrepancies, and recommended corrections. ``` Save this file as `.btw/agents/statistician.md`. The location is what makes it an agent: btw looks for Markdown files in `.btw/agents/` and offers each one to the main chat as a tool. The filename can be anything ending in `.md`; the `name` field identifies the agent. Each field in the frontmatter controls one part of the agent: - `name: statistician` produces a tool named `btw_tool_agent_statistician`. Names can contain letters, numbers, and underscores. - `description` is written for the main chat, which reads it to decide when to delegate a task to this agent. - `title` is the name shown to you in the app. - `tools` works the same way as in `btw.md`, but it limits what the *agent* can use. The statistician gets `run` so it can reproduce a count or a model fit. The body becomes the agent's instructions, added after btw's general instructions for subagents. `files_read` is the only file tool in the list, so the statistician can't write files through a btw file tool. R code can write files, though, so with `run` in the list, the "Do not edit files" instruction is a request, not a sandbox. You don't have to register agent files by hand: `btw_tools()` finds the files in `.btw/agents/` whenever it assembles the chat's tools and the `"agent"` tool group is included. To check that btw found the statistician, run this from your project directory: ```{r, eval = FALSE} names(btw_tools("agent")) ``` The result should include `btw_tool_agent_statistician`, the tool the main chat will call. It also includes `btw_tool_agent_subagent`, btw's built-in tool for one-off delegation, and any agents you've defined at the user level. If the statistician is missing, check that the file is in `.btw/agents/` and that its frontmatter has a `name` field. If you already have an ellmer chat and want to register a particular file yourself, `btw_agent_tool(".btw/agents/statistician.md")` creates its tool definition directly. ## Add the other two agents The researcher and the fact checker use the same format. Their jobs only require reading, so their `tools` lists leave out `run`. Create `.btw/agents/researcher.md`: ``` text --- name: researcher title: Researcher description: Review a study's question, data provenance, design, and causal claims. tools: [files_read, files_search, docs] --- You are reviewing a research report. Read the report named in the task. Check the research question, where the data came from, the study design, and whether the conclusions claim more than that design supports. Do not edit files. Return a short memo with the report's strengths, concerns, and claims the author should revise. Cite the passages you checked. ``` Then create `.btw/agents/fact_checker.md`, with the same tools and a different job: ``` text --- name: fact_checker title: Fact checker description: Check the final report against the source and earlier review memos. tools: [files_read, files_search, docs] --- Read the report named in the task and compare its claims with the researcher and statistician memos included in your task. Flag unsupported or contradictory claims, citing the report and the relevant memo. Do not edit files. Return a concise list of corrections. ``` The fact checker's instructions refer to memos "included in your task", and that wording matters. The fact checker's session has no access to the researcher's or statistician's sessions. Their memos reached the main chat as tool results. For the fact checker to compare the report with them, the main chat has to copy the memos into the prompt when it calls `btw_tool_agent_fact_checker`. ## Give an agent its own model An agent file can have a `client` field, written the same way as in `btw.md`. Use it when a job suits a different model than the main chat's. For example, the researcher's job is narrow, so you could give it a smaller, faster model by adding a `client` line to its frontmatter and leaving the body as it was: ``` text --- name: researcher title: Researcher description: Review a study's question, data provenance, design, and causal claims. client: posit/zai-org/GLM-5.3-Flash tools: [files_read, files_search, docs] --- ``` Without a `client` field, an agent uses the `btw.subagent.client` option if you've set it, for example in the `options` of your `btw.md` file. Otherwise, it uses the client from the `btw.client` option or from your `btw.md` file. Agent files can also live at the user level, for example in `~/.btw/agents/`, when you want to reuse an agent across projects. When a project agent and a user-level agent have the same name, btw uses the project agent. ## Run the review With the three files in place, give the main chat a report path and the order of the calls. This is an example prompt to type into `btw_app()`; replace `penguin-study.qmd` with a report in your project. > Review `penguin-study.qmd` using the custom agents. > Call `researcher` to assess the question, provenance, design, and causal language. > Call `statistician` to reproduce the sample-size count and model and check the numerical claims. > Then call `fact_checker`, including the complete researcher and statistician memos in its task. > Summarize the corrections I should make to the report, distinguishing verified results from unresolved questions. If the model follows these instructions, the main chat makes three tool calls, one after another. It calls `btw_tool_agent_researcher` with a prompt naming the report, and the researcher's memo comes back as the result. It calls `btw_tool_agent_statistician` the same way. Then it calls `btw_tool_agent_fact_checker` with a prompt that includes both memos. With all three results in its context, the main chat writes the summary you asked for. Each call starts a new session unless the main chat passes the `session_id` from an earlier call to continue that session. A tool result is text, not an edit to the report. The main chat can use its own tools to check the memos or act on them. The prompt we used here was overly explicit; in practice I'd include coordinating instructions for when and how to call the subagents in the project's `btw.md` file. In general, you usually don't need to be as explicit, the main agent will call the appropriate subagent according to the instructions in the agent's `description`. ## Work with an agent directly Because an agent file uses the `btw.md` format, it can also configure a chat on its own. To chat with the statistician without asking the main chat to delegate, pass its file as `path_btw`: ```{r, eval = FALSE} btw_app( client = ellmer::chat_posit(), path_btw = ".btw/agents/statistician.md" ) ``` btw reads the file the way it reads `btw.md`: the file's `tools` become the chat's tools, and its body is added to the chat's system prompt. This is a regular `btw_app()` conversation, not a subagent session that reports back to another chat. The file takes the place of the project's `btw.md`, so the project's `client` setting doesn't apply; that's why this call passes `client`, though a `client` field in the agent file would work too. Your user-level `btw.md`, if you have one, still applies as usual. The `name`, `title`, and `description` fields only matter when btw offers the file as a tool, so the chat ignores them. ## Recap We started from a `btw.md` file that configures the chat you talk to. By adding a `name` and a `description` and saving the file in `.btw/agents/`, you can turn the same format into an agent that the main chat can call. When the main chat calls an agent's tool, btw runs a new session with that agent's tools, instructions, and optionally its own model, and returns the session's final response as the tool result. Apart from the agent's own instructions, the main chat's prompt is the only input the session gets, and the tool result is the only output the main chat sees. You can also delegate a one-off task without making an agent file: `btw_tool_agent_subagent` lets the main chat supply a prompt and tool list for that call. Use a custom agent file when the job, instructions, and tool limits should be repeatable or shared with the project.