--- title: "A deliberation experiment: group composition and collective decisions" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{A deliberation experiment: group composition and collective decisions} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include=FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = identical(tolower(Sys.getenv("LLMRAGENT_RUN_VIGNETTES", "false")), "true") ) ``` Agent-based simulation with language models can pose questions that are hard to randomize with human participants, at low cost and with full transcripts: does group composition change a collective decision? Here we run a small factorial deliberation study, the kind a class or a pilot grant could extend. The design is small: panels of three deliberate on a workplace proposal and then vote privately. We vary one factor, whether the panel contains a fiscal skeptic, and replicate each cell. A caution before the code: simulated agents are models of discourse, not of people. Results characterize the model under the personas given, a useful tool for theory development and instrument piloting, not a substitute for human subjects. ```{r design} library(LLMRagent) cfg <- LLMR::llm_config("groq", "openai/gpt-oss-20b", temperature = 0.9) base_personas <- c( "An operations manager who values predictability. Plain speech.", "A young engineer enthusiastic about flexible work. Optimistic." ) skeptic <- "A finance director fixated on costs and risks. Blunt." neutral <- "An HR generalist who weighs evidence carefully. Even-keeled." design <- expand.grid( composition = c("with_skeptic", "no_skeptic"), stringsAsFactors = FALSE ) run_cell <- function(cond, rep) { third <- if (cond$composition == "with_skeptic") skeptic else neutral panel <- list( agent("Morgan", cfg, persona = base_personas[1], quiet = TRUE), agent("Sam", cfg, persona = base_personas[2], quiet = TRUE), agent("Ren", cfg, persona = third, quiet = TRUE) ) deliberate( panel, proposal = "Adopt a four-day work week for a one-year pilot, full pay.", rounds = 2, quiet = TRUE ) } ``` Run the experiment (2 cells x 5 replications = 10 deliberations; with `LLMR::llm_log_enable()` on, every call is archived): ```{r run} LLMR::llm_log_enable("deliberation_runs.jsonl") res <- agent_experiment(design, run_cell, reps = 5, quiet = TRUE) LLMR::llm_log_disable() res[, c("composition", "rep", "error", "duration")] ``` Tidy the outcomes and compare: ```{r analyze} votes <- do.call(rbind, lapply(seq_len(nrow(res)), function(i) { d <- res$result[[i]] if (is.null(d)) return(NULL) cbind(composition = res$composition[i], rep = res$rep[i], d$votes) })) # share of 'yes' votes by composition aggregate(I(vote == "yes") ~ composition, data = votes, FUN = mean) # and the decisions decisions <- vapply(res$result, function(d) if (is.null(d)) NA_character_ else d$decision, character(1)) table(res$composition, decisions, useNA = "ifany") ``` Everything is inspectable: each `res$result[[i]]$transcript` is a tidy utterance-level frame (speaker, round, text) for content analysis, and the private votes carry one-sentence reasons. Natural extensions: vary the proposal framing as a second factor, replace the vote schema with a continuous support scale, score transcripts with `LLMR::llm_mutate()` for argument types, or re-run the same design across providers to check that a finding is not one model's idiosyncrasy.