Nothing
#' @title Agent System: Multi-Agent Orchestration
#' @description
#' Agent R6 class for building multi-agent systems. An Agent is a stateless
#' worker unit that holds capability (tools, persona) while the Session holds
#' conversation state.
#' @name agent
NULL
#' @title Agent Class
#' @description
#' R6 class representing an AI agent. Agents are the worker units in the
#' multi-agent architecture. Each agent has a name, description (for semantic
#' routing), system prompt (persona), and a set of tools it can use.
#'
#' Key design principle: Agents are stateless regarding conversation history.
#' The ChatSession holds the shared state (history, memory, environment).
#' @export
Agent <- R6::R6Class(
"Agent",
public = list(
#' @field name Unique identifier for this agent.
name = NULL,
#' @field description Description of the agent's capability.
#' This is the "API" that the LLM Manager uses for semantic routing.
description = NULL,
#' @field system_prompt The agent's persona/instructions.
system_prompt = NULL,
#' @field tools List of Tool objects this agent can use.
tools = NULL,
#' @field skill_registry Optional registry of local skills available to the agent.
skill_registry = NULL,
#' @field model Default model ID for this agent.
model = NULL,
#' @field capability_models Optional capability-specific model routes.
capability_models = NULL,
#' @description Initialize a new Agent.
#' @param name Unique name for this agent (e.g., "DataCleaner", "Visualizer").
#' @param description A clear description of what this agent does.
#' This is used by the Manager LLM to decide which agent to delegate to.
#' @param system_prompt Optional system prompt defining the agent's persona.
#' @param tools Optional list of Tool objects the agent can use.
#' @param skills Optional character vector of skill paths or "auto" to discover skills.
#' When provided, this automatically loads skills, creates tools, and updates the system prompt.
#' @param model Optional default model ID for this agent.
#' @param capability_models Optional named list of capability-specific model routes.
#' @return An Agent object.
initialize = function(name,
description,
system_prompt = NULL,
tools = NULL,
skills = NULL,
model = NULL,
capability_models = NULL) {
if (missing(name) || !is.character(name) || nchar(name) == 0) {
rlang::abort("Agent 'name' is required and must be a non-empty string.")
}
if (missing(description) || !is.character(description) || nchar(description) == 0) {
rlang::abort("Agent 'description' is required and must be a non-empty string.")
}
self$name <- name
self$description <- description
self$model <- model
self$capability_models <- normalize_capability_model_routes(capability_models %||% list())
# Handle skills
skill_prompt <- NULL
skill_tools <- list()
if (!is.null(skills)) {
registry <- coerce_skill_registry(skills, recursive = TRUE, project_dir = getwd())
if (registry$count() > 0) {
# Generate prompt section
skill_prompt <- registry$generate_prompt_section()
}
if (registry$count() > 0 || nrow(registry$list_roots()) > 0) {
# Create tools
skill_tools <- create_skill_tools(registry)
self$skill_registry <- registry
}
}
# Combine system prompt
if (!is.null(skill_prompt) && nzchar(skill_prompt)) {
system_prompt <- if (is.null(system_prompt)) {
skill_prompt
} else {
paste(system_prompt, "\n\n", skill_prompt, sep = "")
}
}
self$system_prompt <- system_prompt
self$tools <- c(tools %||% list(), skill_tools)
},
#' @description Run the agent with a given task.
#' @param task The task instruction (natural language).
#' @param session Optional ChatSession for shared state. If NULL, a temporary
#' session is created.
#' @param context Optional additional context to inject (e.g., from parent agent).
#' @param model Optional model override. Uses session's model if not provided.
#' @param max_steps Maximum ReAct loop iterations. Default 10.
#' @param ... Additional arguments passed to generate_text.
#' @return A GenerateResult object from generate_text.
run = function(task,
session = NULL,
context = NULL,
model = NULL,
max_steps = 10,
...) {
# Build the effective system prompt with context injection
effective_system <- private$build_system_prompt(context, session)
# Determine the model to use
if (is.null(model)) {
if (!is.null(session)) {
# Try to get model from session
model <- session$get_model_id()
}
if (is.null(model)) {
# Try to get model from agent's default
model <- self$model
}
if (is.null(model)) {
rlang::abort("No model specified. Provide 'model' argument, use a session with a model, or set agent default model.")
}
}
# Execute the ReAct loop using the core API
# Pass session so tools can access/modify the shared environment
result <- generate_text(
model = model,
prompt = task,
system = effective_system,
tools = self$tools,
max_steps = max_steps,
session = session,
...
)
# If we have a session, record this interaction
if (!is.null(session)) {
# Store the task and result in session memory
session$set_memory(
paste0("agent_", self$name, "_last_task"),
task
)
session$set_memory(
paste0("agent_", self$name, "_last_result"),
result$text
)
}
result
},
#' @description Run the agent with streaming output.
#' @param task The task instruction (natural language).
#' @param callback Function to handle streaming chunks: callback(text, done).
#' @param session Optional ChatSession for shared state.
#' @param context Optional additional context to inject.
#' @param model Optional model override.
#' @param max_steps Maximum ReAct loop iterations. Default 10.
#' @param ... Additional arguments passed to stream_text.
#' @return A GenerateResult object (accumulated).
stream = function(task,
callback = NULL,
session = NULL,
context = NULL,
model = NULL,
max_steps = 10,
...) {
# Build the effective system prompt with context injection
effective_system <- private$build_system_prompt(context, session)
# Determine the model to use
if (is.null(model)) {
if (!is.null(session)) {
model <- session$get_model_id()
}
if (is.null(model)) {
# Try to get model from agent's default
model <- self$model
}
if (is.null(model)) {
rlang::abort("No model specified. Provide 'model' argument, use a session with a model, or set agent default model.")
}
}
# Execute the streaming ReAct loop
result <- stream_text(
model = model,
prompt = task,
callback = callback,
system = effective_system,
tools = self$tools,
max_steps = max_steps,
session = session,
...
)
# If we have a session, record this interaction
if (!is.null(session)) {
# Store the task and result in session memory
session$set_memory(
paste0("agent_", self$name, "_last_task"),
task
)
session$set_memory(
paste0("agent_", self$name, "_last_result"),
result$text
)
}
invisible(result)
},
#' @description Convert this agent to a Tool.
#' @details
#' This allows the agent to be used as a delegate target by a Manager agent.
#' The tool wraps the agent's run() method and uses the agent's description
#' for semantic routing.
#' @return A Tool object that wraps this agent.
as_tool = function() {
agent_ref <- self
Tool$new(
name = paste0("delegate_to_", self$name),
description = paste0(
"Delegate a task to the ", self$name, " agent. ",
self$description
),
parameters = z_object(
task = z_string("The task to delegate to this agent."),
context = z_string("Optional additional context for the agent.")
),
execute = function(task, context = NULL) {
result <- agent_ref$run(task = task, context = context)
# Return the text result for the Manager to process
result$text
}
)
},
#' @description Create a stateful ChatSession from this agent.
#' @param model Optional model override.
#' @param ... Additional arguments passed to ChatSession$new.
#' @return A ChatSession object initialized with this agent's config.
create_session = function(model = NULL, ...) {
ChatSession$new(
model = model,
agent = self,
...
)
},
#' @description Print method for Agent.
print = function() {
cat("<Agent>\n")
cat(" Name:", self$name, "\n")
cat(" Description:", substr(self$description, 1, 60), "...\n")
cat(" System Prompt:", if (!is.null(self$system_prompt)) "defined" else "none", "\n")
cat(" Tools:", length(self$tools), "tools\n")
invisible(self)
}
),
private = list(
# Build the effective system prompt with context injection.
build_system_prompt = function(context = NULL, session = NULL) {
parts <- character(0)
# Add base system prompt if defined
if (!is.null(self$system_prompt)) {
parts <- c(parts, self$system_prompt)
}
# Add context injection if provided
if (!is.null(context)) {
context_block <- paste0(
"\n\n[CURRENT CONTEXT]\n",
context
)
parts <- c(parts, context_block)
}
# Add session environment summary if provided
if (!is.null(session)) {
session_info <- character(0)
# Summarize shared environment
env_vars <- session$list_envir()
if (length(env_vars) > 0) {
env_summary <- paste(sapply(env_vars, function(v) {
obj <- session$get_envir()[[v]]
paste0("- ", v, " (", paste(class(obj), collapse = ", "), ")")
}), collapse = "\n")
session_info <- c(session_info, "Objects in shared environment:", env_summary)
}
# Summarize shared memory
mem_keys <- session$list_memory()
if (length(mem_keys) > 0) {
# Filter out internal keys like 'agent_..._last_task' to reduce noise, if desired.
# For now, we list everything to be transparent.
mem_summary <- paste0("- ", mem_keys, collapse = "\n")
session_info <- c(session_info, "Keys in shared memory:", mem_summary)
}
if (length(session_info) > 0) {
session_block <- paste0(
"\n\n[SHARED SESSION CONTEXT]\n",
"The following objects and memory are available in the shared session. You can use available tools to access them.\n",
paste(session_info, collapse = "\n")
)
parts <- c(parts, session_block)
}
}
if (length(parts) == 0) {
return(NULL)
}
paste(parts, collapse = "\n")
}
)
)
#' @title Create an Agent
#' @description
#' Factory function to create a new Agent object.
#' @param name Unique name for this agent.
#' @param description A clear description of what this agent does.
#' @param system_prompt Optional system prompt defining the agent's persona.
#' @param tools Optional list of Tool objects the agent can use.
#' @param skills Optional character vector of skill paths or "auto".
#' @param model Optional default model ID for this agent.
#' @param capability_models Optional named list of capability-specific model routes.
#' @return An Agent object.
#' @export
#' @examples
#' \donttest{
#' if (interactive()) {
#' # Create a simple math agent
#' math_agent <- create_agent(
#' name = "MathAgent",
#' description = "Performs arithmetic calculations",
#' system_prompt = "You are a math assistant. Return only numerical results."
#' )
#'
#' # Run the agent
#' result <- math_agent$run("Calculate 2 + 2", model = "openai:gpt-4o")
#'
#' # Create an agent with skills
#' stock_agent <- create_agent(
#' name = "StockAnalyst",
#' description = "Stock analysis agent",
#' skills = "auto"
#' )
#' }
#' }
create_agent <- function(name,
description,
system_prompt = NULL,
tools = NULL,
skills = NULL,
model = NULL,
capability_models = NULL) {
Agent$new(
name = name,
description = description,
system_prompt = system_prompt,
tools = tools,
skills = skills,
model = model,
capability_models = capability_models
)
}
# Null-coalescing operator (if not already defined)
if (!exists("%||%")) {
`%||%` <- function(x, y) if (is.null(x)) y else x
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.