Overview
RunWorkflow() and RunWorkflows() accept an
optional lConfig object with two data hooks:
-
LoadData, called before workflow spec validation and before the first step runs. -
SaveData, called after the workflow steps finish whenbReturnResult = TRUE.
Hooks make workflow definitions portable. The workflow can describe
what to run, while lConfig describes where data should come
from and where results should go in a particular environment.
Example workflow
The examples below use a small workflow that summarizes a measure from an analysis data frame. The workflow does not know how the data is loaded or saved.
summarise_measure <- function(adsl, measure) {
data.frame(
measure = measure,
n = nrow(adsl),
mean = mean(adsl[[measure]])
)
}
lWorkflow <- list(
meta = list(
Type = "demo",
ID = "height",
Source = "analysis"
),
steps = list(
list(
name = "summarise_measure",
output = "summary",
params = list(
adsl = "adsl",
measure = "HEIGHTBL"
)
)
)
)
analysis_data <- list(
analysis = list(
adsl = data.frame(
USUBJID = sprintf("SUBJ%03d", 1:4),
HEIGHTBL = c(160, 172, 168, 181)
)
)
)Inline hooks
For a single project, the simplest configuration is to provide
functions directly in lConfig.
LoadData must accept lWorkflow,
lConfig, and lData. It returns the data list
that RunWorkflow() should use.
SaveData must accept lWorkflow and
lConfig. It can read the completed workflow, including
lWorkflow$lData and lWorkflow$lResult.
saved_results <- new.env(parent = emptyenv())
lConfig <- list(
data_store = analysis_data,
result_store = saved_results,
LoadData = function(lWorkflow, lConfig, lData) {
source_name <- lWorkflow$meta$Source
c(lData, lConfig$data_store[[source_name]])
},
SaveData = function(lWorkflow, lConfig) {
result_name <- paste(lWorkflow$meta$Type, lWorkflow$meta$ID, sep = "_")
assign(result_name, lWorkflow$lResult, envir = lConfig$result_store)
invisible(NULL)
}
)
result <- workr::RunWorkflow(
lWorkflow = lWorkflow,
lData = list(),
lConfig = lConfig
)
result
#> measure n mean
#> 1 HEIGHTBL 4 170.25
saved_results$demo_height
#> measure n mean
#> 1 HEIGHTBL 4 170.25Registered providers
When the same loading or saving behavior is shared across workflows
or projects, register a named provider once and reference it from
lConfig by name.
workr::register_load_provider(
"vignette.memory.load",
function(lWorkflow, lConfig, lData) {
source_name <- lWorkflow$meta$Source
c(lData, lConfig$data_store[[source_name]])
}
)
workr::register_save_provider(
"vignette.memory.save",
function(lWorkflow, lConfig) {
result_name <- paste(lWorkflow$meta$Type, lWorkflow$meta$ID, sep = "_")
assign(result_name, lWorkflow$lResult, envir = lConfig$result_store)
invisible(NULL)
}
)
registered_results <- new.env(parent = emptyenv())
lRegisteredConfig <- list(
data_store = analysis_data,
result_store = registered_results,
LoadData = "vignette.memory.load",
SaveData = "vignette.memory.save"
)
workr::RunWorkflow(
lWorkflow = lWorkflow,
lData = list(),
lConfig = lRegisteredConfig
)
#> measure n mean
#> 1 HEIGHTBL 4 170.25
registered_results$demo_height
#> measure n mean
#> 1 HEIGHTBL 4 170.25Provider functions are validated when they are registered.
LoadData providers must include named formals
lWorkflow, lConfig, and lData;
SaveData providers must include named formals
lWorkflow and lConfig.
Using hooks with multiple workflows
RunWorkflows() passes the same lConfig
through to each workflow. This lets a project use one loading and saving
strategy while each workflow selects the data it needs from
meta.
lWeightWorkflow <- lWorkflow
lWeightWorkflow$meta$ID <- "weight"
lWeightWorkflow$steps[[1]]$params$measure <- "WEIGHTBL"
analysis_data$analysis$adsl$WEIGHTBL <- c(60, 72, 68, 81)
multi_results <- new.env(parent = emptyenv())
lRegisteredConfig$result_store <- multi_results
lRegisteredConfig$data_store <- analysis_data
workr::RunWorkflows(
lWorkflows = list(lWorkflow, lWeightWorkflow),
lData = list(),
lConfig = lRegisteredConfig
)
#> $demo_height
#> measure n mean
#> 1 HEIGHTBL 4 170.25
#>
#> $demo_weight
#> measure n mean
#> 1 WEIGHTBL 4 70.25
multi_results$demo_height
#> measure n mean
#> 1 HEIGHTBL 4 170.25
multi_results$demo_weight
#> measure n mean
#> 1 WEIGHTBL 4 70.25lConfig can be either a list or an environment. This is
useful when an existing application already stores configuration in an
environment, such as a Shiny app or project runtime.
env_results <- new.env(parent = emptyenv())
lEnvironmentConfig <- list2env(
list(
data_store = analysis_data,
result_store = env_results,
LoadData = "vignette.memory.load",
SaveData = "vignette.memory.save"
),
parent = emptyenv()
)
workr::RunWorkflow(
lWorkflow = lWorkflow,
lData = list(),
lConfig = lEnvironmentConfig
)
#> measure n mean
#> 1 HEIGHTBL 4 170.25
env_results$demo_height
#> measure n mean
#> 1 HEIGHTBL 4 170.25Loading simulated data
workr also includes a built-in registered
LoadData provider named "gsm.datasim". The
provider uses the optional gsm.datasim package to generate
input data, adds legacy df* aliases for generated
Raw_* objects, and merges those generated objects with any
caller-supplied lData.
Install or update the optional dependency from the dev
branch before running the example locally:
pak::pak("Gilead-BioStats/gsm.datasim@dev")The example is evaluated only when gsm.datasim is
installed.
summarise_enrollment <- function(Raw_SUBJ) {
aggregate(
Raw_SUBJ$subjid,
by = list(site_id = Raw_SUBJ$invid),
FUN = length
) |>
setNames(c("site_id", "enrolled_subjects"))
}
lDatasimWorkflow <- list(
meta = list(Type = "demo", ID = "datasim_enrollment"),
steps = list(
list(
name = "summarise_enrollment",
output = "enrollment_summary",
params = list(Raw_SUBJ = "Raw_SUBJ")
)
)
)
datasim_results <- new.env(parent = emptyenv())
workr::RunWorkflow(
lWorkflow = lDatasimWorkflow,
lData = list(),
lConfig = list(
LoadData = "gsm.datasim",
SaveData = function(lWorkflow, lConfig) {
assign("enrollment_summary", lWorkflow$lResult, envir = lConfig$result_store)
assign("loaded_names", sort(names(lWorkflow$lData)), envir = lConfig$result_store)
invisible(NULL)
},
result_store = datasim_results,
gsm.datasim = list(
profile = "standard",
study_type = "standard",
participants = 25,
sites = 4,
snapshot_count = 1
)
)
)
#> INFO [2026-06-25 18:57:15] -- Adding snapshot 1...
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_SITE...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_SITE added successfully
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_SUBJ...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_SUBJ added successfully
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_ENROLL...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_ENROLL added successfully
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_VISIT...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_VISIT added successfully
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_STUDCOMP...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_STUDCOMP added successfully
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_AE...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_AE added successfully
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_IE...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_IE added successfully
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_LB...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_LB added successfully
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_OverallResponse...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_OverallResponse added successfully
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_PD...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_PD added successfully
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_Randomization...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_Randomization added successfully
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_SDRGCOMP...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_SDRGCOMP added successfully
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_DATACHG...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_DATACHG added successfully
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_DATAENT...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_DATAENT added successfully
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_Death...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_Death added successfully
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_PK...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_PK added successfully
#> INFO [2026-06-25 18:57:15] ---- Adding dataset Raw_QUERY...
#> INFO [2026-06-25 18:57:15] ---- Dataset Raw_QUERY added successfully
#> INFO [2026-06-25 18:57:15] -- Snapshot 1 added successfully
#> site_id enrolled_subjects
#> 1 0X1980 6
#> 2 0X2614 7
#> 3 0X3371 7
#> 4 0X9089 5
head(datasim_results$enrollment_summary)
#> site_id enrolled_subjects
#> 1 0X1980 6
#> 2 0X2614 7
#> 3 0X3371 7
#> 4 0X9089 5
head(datasim_results$loaded_names)
#> [1] "dfAE" "dfDATACHG" "dfDATAENT" "dfDeath" "dfENROLL" "dfIE"Use lConfig$gsm.datasim to tune the simulated study.
Common fields include participants, sites,
snapshot_count, months_duration,
snapshot, profile, and
study_type.
For longitudinal settings, the provider returns the latest snapshot
by default, or the snapshot selected by
lConfig$gsm.datasim$snapshot.
GitHub Actions artifact operations
The built-in "github_artifact" providers support the
operational cycle used in CI:
- Load inputs, for example with
LoadData = "gsm.datasim". - Run the workflow steps.
- Save selected
lDataentries withSaveData = "github_artifact". - Restore the saved artifact in a later run with
LoadData = "github_artifact".
The save provider writes a local bundle containing
manifest.yaml and one .rds payload file per
selected lData entry. If the bundle should be available to
later GitHub Actions jobs, upload the bundle directory after
RunWorkflow() completes, for example with
actions/upload-artifact.
lConfig <- list(
LoadData = "gsm.datasim",
SaveData = "github_artifact",
gsm.datasim = list(
profile = "standard",
study_type = "standard",
snapshot_count = 1
),
github_artifact = list(
artifact_name = "workr-study-state",
include = c("Raw_SUBJ", "dfSUBJ", "result")
)
)A later run can restore that bundle by naming the repository and
either an explicit run_id or a restore policy:
lConfig <- list(
LoadData = "github_artifact",
github_artifact = list(
repo = "owner/repo",
artifact_name = "workr-study-state",
policy = "latest_success"
)
)Restoring artifacts uses the GitHub Actions API to list workflow
runs, list artifacts for the selected run, and download the artifact
bundle. In GitHub Actions, expose a token to R as GH_TOKEN
or GITHUB_PAT and grant at least:
For fine-grained personal access tokens, grant repository access plus Actions: Read.
Common restore failures usually point to one of three setup issues:
the token is missing or lacks actions: read,
artifact_name or repo points at the wrong
artifact, or the uploaded artifact did not contain the full bundle
directory with manifest.yaml at its root.