Skip to contents

This example asks an agent to explore a dataset, run R calculations, and report what it finds. The agent chooses its next calculation from the results of earlier ones. For a fixed sequence of steps, see the extraction pipeline.

The example permits R code to run in a separate process with your user account’s access. For untrusted inputs or prompts, use an OS sandbox such as mcp-repl through tools_mcp_repl().

Setup

We create an agent with data and code tools, plus the built-in data_analysis skill:

library(deputy)

chat <- ellmer::chat_anthropic(model = "claude-sonnet-4-20250514")

agent <- Agent$new(
  chat = chat,
  tools = c(tools_data(), tools_code()),
  permissions = Permissions(
    file_read = TRUE,
    file_write = FALSE,
    r_code = TRUE
  ),
  system_prompt = "You are a data scientist. When exploring a dataset:
    1. Start with structure and summary statistics
    2. Check for missing values and data quality issues
    3. Examine distributions of key variables
    4. Look for interesting relationships and correlations
    5. Summarise your findings clearly

    Use R code for all analysis. Show your reasoning at each step."
)

# Load the built-in data analysis skill for extra tools
skill_path <- system.file("skills/data_analysis", package = "deputy")
agent$load_skill(skill_path)

The data_analysis skill adds specialised tools like eda_summary and describe_column that complement the general-purpose run_r_code tool.

Running the Analysis

With run_sync(), the agent works through the analysis autonomously. It calls tools, inspects results, and decides what to do next:

result <- agent$run_sync(
  "Explore the airquality dataset that comes with R. Give me a
   thorough understanding of the data including quality issues,
   distributions, and interesting patterns.",
  usage_limits = UsageLimits(max_requests = 15)
)

cat(result$response)

The agent typically:

  1. Loads the data and examines its structure
  2. Checks for missing values (airquality has NAs in Ozone and Solar.R)
  3. Computes summary statistics for each variable
  4. Looks at correlations between Ozone, Solar.R, Wind, and Temp
  5. Investigates seasonal patterns across Month
  6. Summarises findings

Streaming Output

For interactive use, run() streams events as they happen. You see text arrive token by token and tools fire in real time:

chat <- ellmer::chat_anthropic(model = "claude-sonnet-4-20250514")

agent <- Agent$new(
  chat = chat,
  tools = c(tools_data(), tools_code()),
  permissions = Permissions(
    file_read = TRUE,
    file_write = FALSE,
    r_code = TRUE
  ),
  system_prompt = "You are a data scientist. Be thorough but concise."
)

events <- agent$run("Summarise the mtcars dataset")
repeat {
  event <- events()
  if (coro::is_exhausted(event)) {
    break
  }
  switch(event$type,
    "text" = cat(event$text),
    "tool_start" = cli::cli_alert_info("Calling {event$tool_name}..."),
    "tool_end" = cli::cli_alert_success("{event$tool_name} done"),
    "stop" = cli::cli_alert("Finished in {event$total_turns} turns")
  )
}

Inspecting Results

AgentResult captures everything about the run:

# Did it succeed?
result_is_success(result)
result$stop_reason

# How many turns did the agent take?
result_n_turns(result)

# What tools were called?
tool_calls <- result_tool_calls(result)
length(tool_calls)

# How long and how much?
result$duration
result$cost

Adding Guardrails

For production use, add permissions and hooks to monitor and constrain the agent:

chat <- ellmer::chat_anthropic(model = "claude-sonnet-4-20250514")

agent <- Agent$new(
  chat = chat,
  tools = c(tools_data(), tools_code()),
  permissions = Permissions(
    r_code = TRUE,
    bash = FALSE,
    file_write = FALSE
  ),
  usage_limits = UsageLimits(max_requests = 20, max_cost_usd = 0.50)
)

# Log every tool call
agent$add_hook(hook_log_tools(verbose = TRUE))

# Block dangerous bash commands (if bash were enabled)
agent$add_hook(hook_block_dangerous_bash())

result <- agent$run_sync(
  "Analyse the airquality dataset and report key findings."
)

The agent can read data and run R code, but cannot write files or run bash commands. This permission is application policy, not an OS sandbox. Deputy stops the run when it observes either configured usage limit, and stops with "cost_unavailable" if the provider does not report enough cost information to enforce the dollar limit.

Next Steps