Introduction

This post demonstrates how to simulate a political debate using large language models and R. The example builds a hypothetical debate for the 2025 Albuquerque mayoral race, with each candidate represented by an LLM agent that responds based on their official questionnaire submissions. A moderator agent manages the debate flow, poses questions, and facilitates candidate exchanges.

The approach is straightforward and offers a practical template for simulating multi-party dialogues where each participant maintains a distinct perspective grounded in source material.

The Candidates

  • Daniel Chavez - Small business owner
  • Darren White - Former public safety director
  • Mayling Armijo - Community advocate
  • Eddie Varela - Business owner
  • Louie Sanchez - City Councilor

Note: Not all candidates in the race completed questionnaires, including incumbent Mayor Tim Keller, who is not included in this simulation.


Setup and Dependencies

Required R libraries are loaded using pacman. Candidate questionnaire file locations are mapped to their GitHub URLs.

if (!requireNamespace("pacman", quietly = TRUE)) {
  install.packages("pacman")
}

suppressPackageStartupMessages(
  pacman::p_load(
    ellmer,
    glue,
    purrr,
    jsonlite
  )
)

# Base URL for candidate questionnaires on GitHub
QUESTIONNAIRE_BASE_URL <- "https://raw.githubusercontent.com/jaytimm/newmexico-political-data/main/data/abq_mayor_2025_questionnaires"

# Candidate file mapping with GitHub URLs
CANDIDATE_FILES <- list(
  "Daniel Chavez" = paste0(QUESTIONNAIRE_BASE_URL, "/Official.Daniel.Chavez.Response_250609.md"),
  "Darren White" = paste0(QUESTIONNAIRE_BASE_URL, "/Official.White_.Darren.Response._250609.md"),
  "Mayling Armijo" = paste0(QUESTIONNAIRE_BASE_URL, "/Armijo.Mayling.Response_250609.md"),
  "Eddie Varela" = paste0(QUESTIONNAIRE_BASE_URL, "/Eddie-Varela-Mayoral-Candidate-Response.md"),
  "Louie Sanchez" = paste0(QUESTIONNAIRE_BASE_URL, "/Sanchez.Louie_.Response_250609.md")
)

Candidate Questionnaires

Questionnaire responses start as PDFs converted to markdown using docling. Markdown files are stored on GitHub, with original data from the New Mexico Business Coalition’s 2025 Albuquerque Mayoral Candidates page. The code reads each candidate’s questionnaire from GitHub and stores the content for agent system prompts.

# Load all candidate questionnaires from GitHub
candidates_data <- map(names(CANDIDATE_FILES), function(name) {
  url <- CANDIDATE_FILES[[name]]
  tryCatch({
    questionnaire_text <- readLines(url, warn = FALSE) |> paste(collapse = "\n")
    list(
      name = name,
      file = basename(url),
      questionnaire = questionnaire_text
    )
  }, error = function(e) {
    warning("Failed to load questionnaire for ", name, ": ", e$message)
    return(NULL)
  })
}) |>
  purrr::compact()

LLM Agents

Each candidate is represented by an LLM agent with system prompts including their questionnaire responses. An orchestrator agent serves as the debate moderator, managing flow and determining speaking order.

Candidate Agents

Candidate agents are created with system prompts that include questionnaire responses, other candidate names, and instructions to stay in character and highlight key policies with bold markdown. Agents use ChatGPT GPT-4o-mini. A lookup table provides quick access by candidate name. Note that for topics not fully covered in the questionnaire (like Revitalizing Nob Hill), candidates are free to respond based on their knowledge and perspective as a mayoral candidate, not constrained to only questionnaire content.

# Create candidate agents
candidate_agents <- map(candidates_data, function(cand) {
  other_candidates <- Filter(function(n) n != cand$name, map_chr(candidates_data, ~ .x$name))
  
  system_prompt <- glue(
    "You are {cand$name}, running for Mayor of Albuquerque, New Mexico.
    
    Here are your positions from your official candidate questionnaire:
    
    {cand$questionnaire}
    
    The other candidates in this debate are: {paste(other_candidates, collapse=', ')}
    
    NOTE: Some debate topics may not be fully covered in your questionnaire. For those topics, you are free to respond based on your knowledge and perspective as a mayoral candidate for Albuquerque - don't feel constrained to only questionnaire content.
    
    CRITICAL INSTRUCTIONS:
    - You ONLY know what other candidates have said from the debate transcript provided to you
    - You do NOT know their questionnaire responses - you can only reference what they've actually said in this debate
    - When responding, ONLY reference statements that have been made in the debate transcript you receive
    - Stay in character and respond naturally based on YOUR stated positions
    - Answer questions directly - do not dodge or avoid the question asked
    - Keep responses brief and concise - 2-4 sentences maximum
    - NEVER say 'Thank you for the question' - start directly with your answer
    - Be confrontational and antagonistic - directly challenge and disagree with other candidates' proposals when you don't agree
    - Push back on ideas you disagree with - don't be overly polite or diplomatic
    - Show emotion - get upset, frustrated, or passionate when you disagree strongly or feel misrepresented
    - Engage with relevant things other candidates have said - reference their statements, challenge their ideas, build on or disagree with their proposals when relevant
    - Be conversational and debate-like, not formal speeches
    - Jump straight into your answer without pleasantries
    - IMPORTANT: When you mention key ideas, proposals, or specific policies from YOUR questionnaire, format them in **bold** using markdown (e.g., **Home Basis Centers**, **streamline permits**, **decriminalize sleep**). This helps highlight your core positions.
    - Do NOT include your name or 'I am [name]' in your response - just respond directly as the character."
  )
  
  agent <- chat("openai/gpt-4o-mini", system_prompt = system_prompt)
  return(list(name = cand$name, agent = agent))
})

# Create lookup by name for easy access
candidate_lookup <- setNames(candidate_agents, map_chr(candidate_agents, ~ .x$name))

Orchestrator Agent

The orchestrator agent uses ChatGPT GPT-4o-mini and a JSON schema to output structured commands. The schema defines actions (ask_one, request_rebuttal, end_debate), candidate names, and moderator statements. It ensures all candidates speak on each topic before moving to the next.

candidate_names <- map_chr(candidate_agents, ~ .x$name)

debate_topics <- list(
  "Homelessness" = "homelessness",
  "Economic Development" = "economic_development"
)

orchestrator_schema <- ellmer::type_object(
  "Orchestrator command structure",
  action = ellmer::type_string("Action: 'ask_one' (moderator asks), 'request_rebuttal' (candidate responds directly to another), 'end_debate'"),
  candidate = ellmer::type_string("Exact name of the candidate to speak next"),
  moderator_statement = ellmer::type_string("If ask_one: moderator statement. If request_rebuttal: brief context/prompt for direct response."),
  responding_to = ellmer::type_string("If request_rebuttal: name of candidate being responded to. Otherwise empty.")
)

topic_list <- paste(seq_along(debate_topics), names(debate_topics), sep = ". ", collapse = " (3-4 rounds each), ")
orchestrator_prompt <- glue(
  "You are orchestrating a mayoral debate between: {paste(candidate_names, collapse=', ')}. Use a mix of first names and full names when referencing candidates - don't always use first names.",
  "",
  "Topics: {topic_list}",
  "",
  "Moderate naturally. Keep it casual and conversational. Ensure all candidates speak on each topic.",
  "",
  "CRITICAL: Check the transcript carefully. If NO candidates have spoken yet on the current topic, ask a fresh opening question about the topic. Do NOT reference things candidates said if they haven't spoken yet on this topic. Only reference candidate statements that actually appear in the transcript for the current topic. Keep questions brief - one sentence only.",
  "",
  "Actions: 'ask_one' = moderator asks a question. 'request_rebuttal' = candidate responds directly to another candidate (no moderator statement).",
  "",
  "candidate = exact name. If request_rebuttal, set responding_to to the candidate being responded to.",
  sep = "\n"
)

orchestrator <- chat("openai/gpt-4o-mini", system_prompt = orchestrator_prompt)

Helper Functions

Utility functions handle transcript formatting and parsing orchestrator commands into structured data.

Format Transcript for Display

Transcript entries are formatted as markdown strings with speaker labels, with each entry showing the speaker name in bold and concatenated with newlines.

format_transcript <- function(transcript) {
  map_chr(transcript, function(entry) {
    if (entry$speaker == "MODERATOR") {
      glue("\n**MODERATOR:** {entry$text}\n")
    } else {
      glue("**{entry$speaker}:** {entry$text}\n")
    }
  }) |> paste(collapse = "\n")
}

Format Transcript for HTML Output

Transcript entries are formatted as HTML with distinct background colors for each speaker, creating colored boxes with darker left borders. Whitespace is cleaned and each statement is wrapped in styled div elements.

format_transcript_html <- function(transcript) {
  speaker_colors <- list(
    "MODERATOR" = "#f0f0f0",
    "Daniel Chavez" = "#ffe6e6",
    "Darren White" = "#cce6ff",
    "Mayling Armijo" = "#fff4e6",
    "Eddie Varela" = "#f0e6ff",
    "Louie Sanchez" = "#e6ffe6"
  )
  
  darken_color <- function(color) {
    rgb_col <- col2rgb(color)
    darkened <- rgb_col * 0.7
    rgb(darkened[1], darkened[2], darkened[3], maxColorValue = 255)
  }
  
  map_chr(transcript, function(entry) {
    clean_text <- gsub("\n\n+", "\n\n", entry$text)
    bg_color <- if(is.null(speaker_colors[[entry$speaker]])) "#f5f5f5" else speaker_colors[[entry$speaker]]
    
    speaker_label <- if (entry$speaker == "MODERATOR") {
      "MODERATOR"
    } else {
      entry$speaker
    }
    
    glue(
      '<div style="background-color: {bg_color}; padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 4px solid {darken_color(bg_color)};">',
      '<strong>{speaker_label}:</strong><br>',
      '{clean_text}',
      '</div>'
    )
  }) |> paste(collapse = "\n")
}

Get Last N Transcript Entries

The most recent N entries from the transcript are formatted to provide context to the orchestrator during the debate flow.

tail_transcript <- function(transcript, n = 5) {
  start_idx <- max(1, length(transcript) - n + 1)
  format_transcript(transcript[start_idx:length(transcript)])
}

Parse Orchestrator Command

Orchestrator responses are parsed into structured data using the JSON schema, extracting actions, candidate names, and moderator statements. Default values are used for optional fields.

parse_orchestrator_command <- function(orchestrator, input_text) {
  command <- orchestrator$chat_structured(input_text, type = orchestrator_schema)
  list(
    action = if(is.null(command$action)) "ask_one" else command$action,
    candidate = command$candidate,
    moderator_statement = if(is.null(command$moderator_statement)) "Please share your perspective." else command$moderator_statement,
    responding_to = if(is.null(command$responding_to)) "" else command$responding_to
  )
}

The Debate Function

The debate simulation runs through topics, ensuring all candidates speak on each topic before moving to the next. It tracks speaking status and automatically transitions between topics. The function initializes the transcript, enters a loop providing context to the orchestrator, executes commands (asking questions or requesting rebuttals), and records responses. The orchestrator generates a closing statement when the debate ends.

run_debate <- function(topics = debate_topics, max_rounds = 15) {
  topic_keys <- unlist(unname(topics))
  topic_names <- names(topics)
  
  transcript <- list()
  round <- 1
  current_topic_idx <- 1
  current_topic_key <- topic_keys[current_topic_idx]
  
  candidates_spoken <- map(topic_keys, ~ setNames(rep(FALSE, length(candidate_names)), candidate_names))
  names(candidates_spoken) <- topic_keys
  
  topic_list_text <- paste(topic_names, collapse = ", ")
  intro_text <- paste(
    "Good evening. Welcome to the Albuquerque Mayoral Debate.",
    "",
    paste("Tonight we have", length(candidate_names), "candidates:", 
          paste(candidate_names, collapse = ", ")),
    "",
    paste("This debate will focus on", length(topics), "critical issues:"),
    topic_list_text,
    "",
    "Let's begin.",
    sep = "\n"
  )
  transcript <- append(transcript, list(list(speaker = "MODERATOR", text = intro_text)))
  
  while (round <= max_rounds) {
    if (current_topic_idx <= length(topic_keys) && all(candidates_spoken[[current_topic_key]])) {
      current_topic_idx <- current_topic_idx + 1
      if (current_topic_idx > length(topic_keys)) break
      current_topic_key <- topic_keys[current_topic_idx]
    }
    
    current_transcript <- tail_transcript(transcript, n = 5)
    remaining <- names(candidates_spoken[[current_topic_key]])[!candidates_spoken[[current_topic_key]]]
    topic_status <- glue(
      "CURRENT TOPIC: {toupper(topic_names[current_topic_idx])}. ",
      "Remaining: {if(length(remaining)>0) paste(remaining, collapse=', ') else 'All have spoken'}"
    )
    
    # Check if anyone has spoken on current topic
    current_topic_entries <- transcript[which(sapply(transcript, function(e) {
      # Find when current topic started (after previous topic ended or debate started)
      topic_start_idx <- if (current_topic_idx == 1) 1 else {
        prev_topic_key <- topic_keys[current_topic_idx - 1]
        # Find last entry where all candidates spoke on previous topic
        last_prev_idx <- 0
        for (i in seq_along(transcript)) {
          if (transcript[[i]]$speaker != "MODERATOR" && 
              any(sapply(candidate_names, function(n) transcript[[i]]$speaker == n))) {
            last_prev_idx <- i
          }
        }
        last_prev_idx + 1
      }
      # Check if this entry is after topic started
      entry_idx <- which(sapply(seq_along(transcript), function(i) identical(transcript[[i]], e)))
      entry_idx >= topic_start_idx
    }))]
    
    has_spoken_on_topic <- any(sapply(current_topic_entries, function(e) 
      e$speaker != "MODERATOR" && e$speaker %in% candidate_names))
    
    if (has_spoken_on_topic) {
      orchestrator_input <- glue(
        "Current debate transcript:\n\n{current_transcript}\n\n",
        "{topic_status}\n\n",
        "Candidates have already spoken on this topic. Reference relevant things they said in the transcript. Build on their statements, ask follow-ups about their proposals, or challenge their ideas. Output your command in the required format."
      )
    } else {
      orchestrator_input <- glue(
        "Current debate transcript:\n\n{current_transcript}\n\n",
        "{topic_status}\n\n",
        "This is the FIRST question on this topic - no candidates have spoken yet. Ask a fresh opening question about {topic_names[current_topic_idx]}. Do NOT reference things candidates said (they haven't spoken on this topic yet). Output your command in the required format."
      )
    }
    
    command <- parse_orchestrator_command(orchestrator, orchestrator_input)
    
    if (command$action == "end_debate") break
    
    target_name <- command$candidate
    target_cand <- candidate_lookup[[target_name]]
    
    if (is.null(target_cand)) {
      remaining <- names(candidates_spoken[[current_topic_key]])[!candidates_spoken[[current_topic_key]]]
      if (length(remaining) == 0) remaining <- candidate_names
      target_name <- sample(remaining, 1)
      target_cand <- candidate_lookup[[target_name]]
    }
    
    context <- format_transcript(transcript)
    
    if (command$action == "request_rebuttal" && nchar(command$responding_to) > 0) {
      responding_to_name <- command$responding_to
      prompt_text <- command$moderator_statement
      full_prompt <- glue(
        "Here is the complete debate transcript so far:\n\n{context}\n\n",
        "{prompt_text}\n\n",
        "You are responding directly to {responding_to_name}. Be conversational and debate-like.",
        "CRITICAL: You can ONLY reference what other candidates have said in the transcript above."
      )
      response <- target_cand$agent$chat(full_prompt)
      transcript <- append(transcript, list(list(speaker = target_name, text = response)))
    } else {
      moderator_text <- command$moderator_statement
      transcript <- append(transcript, list(list(speaker = "MODERATOR", text = moderator_text)))
      full_prompt <- glue(
        "Here is the complete debate transcript so far:\n\n{context}\n\n",
        "MODERATOR: {moderator_text}\n\n",
        "CRITICAL: You can ONLY reference what other candidates have said in the transcript above. ",
        "Engage with what other candidates have said - challenge their ideas, disagree with their proposals, or build on their statements. ",
        "Do not reference ideas or positions that haven't been mentioned in this debate yet. ",
        "Respond naturally based on the transcript and your own positions."
      )
      response <- target_cand$agent$chat(full_prompt)
      transcript <- append(transcript, list(list(speaker = target_name, text = response)))
    }
    
    candidates_spoken[[current_topic_key]][target_name] <- TRUE
    
    if (all(map_lgl(candidates_spoken, ~ all(.x)))) break
    
    round <- round + 1
    Sys.sleep(0.5)
  }
  
  # Moderator closing statement
  closing_text <- "Thank you to all the candidates for participating in tonight's debate. We appreciate your time and perspectives on these critical issues facing Albuquerque."
  transcript <- append(transcript, list(list(speaker = "MODERATOR", text = closing_text)))
  
  return(transcript)
}

Running the Debate

With all the code above included, you can run the debate directly. Note: This requires an active OpenAI API key with billing enabled. The code checks if a transcript markdown file already exists. If it exists, the transcript is parsed from markdown; otherwise, the debate runs and saves the transcript to the post-specific folder.


Debate Transcript

Note: This is an LLM simulation, not real debate content. All responses are AI-generated based on candidate questionnaire data.

The complete debate transcript is displayed below, with each speaker’s statements shown in distinct colored boxes.

MODERATOR:
Good evening. Welcome to the Albuquerque Mayoral Debate.

Tonight we have 5 candidates: Daniel Chavez, Darren White, Mayling Armijo, Eddie Varela, Louie Sanchez

This debate will focus on 2 critical issues: Homelessness, Economic Development

Let’s begin.
MODERATOR:
To kick things off, how do you view the current state of homelessness in Albuquerque, and what specific measures do you propose to address this issue?

Daniel Chavez:
The current state of homelessness in Albuquerque is dire, and it’s clear that the previous attempts just haven’t been effective. Many candidates seem to throw around ideas without understanding the root issues. For instance, simply adding more temporary shelters hasn’t addressed the underlying problem—many people don’t have stable support systems.

I believe we need a more comprehensive approach with Home Basis Centers, which will not only provide shelter but also essential resources that help people rebuild their lives. Others might focus on band-aid solutions, but it’s time to look at long-term strategies like decriminalizing sleep to offer dignity and safety to the unhoused. We can’t just keep pushing the issue away; we need to tackle it head-on with sustainable solutions that actually work.
MODERATOR:
Darren White, you’ve been listening to Daniel Chavez’s proposals regarding Home Basis Centers and decriminalizing sleep. What are your thoughts on his approach and how would you address the systemic issues surrounding homelessness in Albuquerque?
Darren White:
Daniel, your approach of Home Basis Centers and decriminalizing sleep demonstrates a fundamental misunderstanding of the urgency of the situation. We’ve poured nearly $300 million into homeless programs with little to show for it. Your idea to add more temporary shelters and provide a safety net without enforcing the laws is reckless. Compassion doesn’t mean allowing chaos on our streets. We need to enforce existing laws against illegal encampments and ensure that people experiencing homelessness who refuse help aren’t allowed to set up camp in public spaces. It’s about restoring order, not enabling a cycle of dependency.
MODERATOR:
Mayling Armijo, you’ve heard the perspectives from both Daniel Chavez and Darren White. What’s your view on the effectiveness of their strategies, and what solutions do you think are crucial for addressing homelessness in Albuquerque?

Mayling Armijo:
Darren, your approach is misguided. Simply enforcing laws without addressing the root causes of homelessness will only push people deeper into crisis. We need to support expanding legal safe outdoor spaces and focus on the unique needs of those experiencing homelessness rather than treating them as a nuisance.

Daniel’s idea of Home Basis Centers is a step in the right direction, but let’s not forget that temporary solutions should be paired with comprehensive support services. We must provide housing, healthcare, and job assistance to create lasting change. We can’t just restore order; we need to restore lives.
MODERATOR:
Eddie Varela, you’ve heard the differing views from Daniel, Darren, and Mayling. What solutions do you propose to effectively address the homelessness crisis in Albuquerque, considering the effectiveness of temporary shelters and legal safe outdoor spaces?

Eddie Varela:
The proposals put forth by Daniel and Mayling, such as Home Basis Centers and expanding legal safe outdoor spaces, seem to sidestep the urgent reality we’re facing. While they focus on providing options, they ignore the immediate need to address illegal encampments that are creating chaos on our streets. Darren’s point about enforcing existing laws resonates with my approach; we need a firm stance against illegal encampments.

However, let’s be clear: we can’t just enforce the law. That’s why my strategy involves a detailed, humane plan that includes armed security to engage the homeless community and facilitate a path for compliance. It’s imperative we create accountability while offering solutions for those in crisis, rather than just setting up more temporary measures that don’t tackle the core issue.
MODERATOR:
Louie Sanchez, you’ve heard the arguments around enforcement from Darren and Eddie, along with the proposed solutions from Daniel and Mayling. What is your take on how Albuquerque should tackle the homelessness issue?

Louie Sanchez:
The idea of decriminalizing sleep and adding more legal safe outdoor spaces, as touted by Daniel and Mayling, completely ignores the pressing reality we have on our streets. We cannot normalize living in public spaces; it’s not compassionate or sustainable. We have to act with urgency to provide real, structured pathways to stability, not just band-aid solutions.

Darren and Eddie raise valid points about enforcement, but simply enforcing existing laws without accountability and support for individuals facing homelessness is not a solution. We must have a strategy that includes access to comprehensive support services like mental health treatment, job training, and transitional housing. It’s essential that we establish coordinated outreach and engagement efforts to help those in need while also addressing illegal encampments swiftly and compassionately. We can’t push people further into despair while claiming to restore order; we need a balanced approach that ensures safety and provides real opportunities for individuals to rebuild their lives.
MODERATOR:
Moving on to our second topic, Economic Development. What are your top priorities for boosting economic growth in Albuquerque, and how do you plan to create jobs for our residents?

Daniel Chavez:
The focus on enforcement by candidates like Darren and Eddie misses the mark when it comes to real economic development, especially as it connects to homelessness. Treating homelessness as a nuisance isn’t a viable solution and only drives away potential business development because it creates a negative perception of our city’s safety and livability.

If we truly want to boost economic growth, we need to be business-friendly. My proposal to streamline permits will help small businesses get up and running without suffocating red tape. Adding more temporary solutions to homelessness won’t cut it; we need to address the root issues and create healthier economic conditions. If we’re not investing in our community by providing Home Basis Centers and decriminalizing sleep, we’re missing a critical opportunity to enhance our workforce, making Albuquerque a more attractive place for businesses to thrive. It’s time for a real plan that encourages both recovery for those experiencing homelessness and a robust economy for our residents.
MODERATOR:
What specific strategies do you propose to foster economic growth in Albuquerque, and how will they directly create jobs for our residents?

Darren White:
Daniel, your belief that streamlining permits and focusing on homelessness solutions will automatically lead to economic growth is naive. While it’s essential to make it easier for businesses to operate, what good is that when our city is drowning in chaos from illegal encampments and rampant crime?

You, Eddie, and Mayling want to push forward with temporary measures like Home Basis Centers and expanding legal safe outdoor spaces, but those just enable lawlessness. We must prioritize restoring order first—enforcing existing laws against illegal encampments is critical. Once we create a safe environment, only then can we attract businesses and support job creation. We can’t ignore the link between public safety and economic health; it’s foundational to bringing Albuquerque back on track.
MODERATOR:
Mayling Armijo, with your perspective on economic growth, how do you plan to create jobs for residents in Albuquerque, and what specific strategies will you implement to foster that growth?

Eddie Varela:
Darren’s assertion that enforcing existing laws against illegal encampments alone will restore order and attract businesses is flawed. While safety is crucial, it’s not enough to simply crack down on the visible symptoms of homelessness; we need effective, compassionate solutions as well.

Daniel’s focus on streamlining permits is a step in the right direction, but it doesn’t address the larger crisis. My approach prioritizes not only making the permit process more efficient but also ensuring that we create a business-friendly atmosphere that encourages development. This is about fostering a balance—addressing illegal encampments while also investing in our community—so Albuquerque can thrive economically. We need to implement strong systems that support businesses and provide pathways for those in crisis, not just patchwork solutions that leave the core issues unaddressed.
MODERATOR:
What innovative strategies do you propose to stimulate economic growth and job creation in Albuquerque, particularly in light of current challenges?

Mayling Armijo:
Darren, your insistence that simply enforcing laws will restore order is short-sighted. Yes, public safety is critical, but it has to go hand-in-hand with real economic strategies. Streamlining permits and creating a supportive environment for businesses are essential steps, but without addressing homelessness compassionately, we risk pushing businesses away.

Eddie, while you mention balancing enforcement with development, your ideas still feel disjointed from the real work we need to do. We should focus on expanding legal safe outdoor spaces and providing comprehensive support services so we can integrate those in crisis into our workforce. If we want Albuquerque to thrive, we can’t just look at the surface symptoms; we need solutions that build a solid foundation for economic growth while supporting those who need it most.
MODERATOR:
Louie Sanchez, what are your top priorities for stimulating economic growth in Albuquerque, and how do you plan to create jobs for our residents?

Louie Sanchez:
Addressing the economic growth in Albuquerque cannot be separated from the current homelessness crisis. Darren’s approach insisting solely on enforcement as a solution is overly simplistic and will not attract businesses if our streets remain chaotic. We must tackle the root issues with strategies like streamlining permits to ease the burden on small businesses and create a thriving economic climate.

Daniel, while your focus on Home Basis Centers and decriminalizing sleep might sound compassionate, it risks enabling a cycle of dependency without addressing the immediate need for accountability in our communities. My plan centers on creating a unified business concierge team to assist small businesses in navigating city services efficiently while providing comprehensive support services to individuals experiencing homelessness. We need to create a balance—boosting our economy while ensuring that those in crisis have pathways to stability, rather than just pushing them into temporary shelters or encouraging them to live outside. A robust economy is built on real solutions that support all residents, not just a select few.
MODERATOR:
Thank you to all the candidates for participating in tonight’s debate. We appreciate your time and perspectives on these critical issues facing Albuquerque.

Challenges

Building this simulation revealed several technical challenges common to multi-agent LLM systems:

  • Agents reference information their character shouldn’t know without explicit, repeated constraints
  • Temporal violations occur without external state tracking (e.g., referencing statements before they’re made)
  • LLMs default to formal politeness, requiring negative instructions to maintain debate tone
  • Context windows must be tailored by role—moderators need recent turns, participants need full history
  • All state tracking (turn order, topics, speakers) must be managed in code, not by the LLM

Note: All responses in this simulation are AI-generated based on candidate questionnaire data. This is a technical demonstration, not actual debate content or verified candidate positions.