diff --git a/backend/scripts/run_parallel_simulation.py b/backend/scripts/run_parallel_simulation.py index 3b342794..25b00042 100644 --- a/backend/scripts/run_parallel_simulation.py +++ b/backend/scripts/run_parallel_simulation.py @@ -1156,7 +1156,11 @@ async def run_twitter_simulation( agent_graph=result.agent_graph, platform=oasis.DefaultPlatformType.TWITTER, database_path=db_path, - semaphore=30, # Limit max concurrent LLM requests to prevent API overload + semaphore=8, # Lowered from 30 to bound concurrent LLM responses in memory. + # The OASIS Python process holds each in-flight response in RAM + # until the asyncio.gather() fan-in; with 30 in flight, 2-4 KB + # bios per agent stack up fast. At semaphore=8 the host has + # headroom to run 20-25 agent sims on an 8GB box without OOM. ) await result.env.reset() @@ -1347,7 +1351,11 @@ async def run_reddit_simulation( agent_graph=result.agent_graph, platform=oasis.DefaultPlatformType.REDDIT, database_path=db_path, - semaphore=30, # Limit max concurrent LLM requests to prevent API overload + semaphore=8, # Lowered from 30 to bound concurrent LLM responses in memory. + # The OASIS Python process holds each in-flight response in RAM + # until the asyncio.gather() fan-in; with 30 in flight, 2-4 KB + # bios per agent stack up fast. At semaphore=8 the host has + # headroom to run 20-25 agent sims on an 8GB box without OOM. ) await result.env.reset() diff --git a/backend/vendor/camel-oasis/oasis/social_agent/agents_generator.py b/backend/vendor/camel-oasis/oasis/social_agent/agents_generator.py index 65435906..e8a2fb6f 100644 --- a/backend/vendor/camel-oasis/oasis/social_agent/agents_generator.py +++ b/backend/vendor/camel-oasis/oasis/social_agent/agents_generator.py @@ -581,12 +581,20 @@ async def generate_reddit_agent_graph( "edges": [], # Relationship details "other_info": {}, } - # Update agent profile with additional information - profile["other_info"]["user_profile"] = agent_info[i]["persona"] - profile["other_info"]["mbti"] = agent_info[i]["mbti"] - profile["other_info"]["gender"] = agent_info[i]["gender"] - profile["other_info"]["age"] = agent_info[i]["age"] - profile["other_info"]["country"] = agent_info[i]["country"] + # Update agent profile with additional information. + # Use .get() with sensible defaults so a malformed/missing-field + # profile (which happens when the LLM-emitted persona JSON fails to + # parse and the persona worker falls back to a basic structure) does + # not KeyError the whole simulation at start time. Hard-coding + # defaults here means a sim can run with a few sparse profiles + # instead of crashing on round 1. + profile["other_info"]["user_profile"] = ( + agent_info[i].get("persona") or agent_info[i].get("bio", "") + ) + profile["other_info"]["mbti"] = agent_info[i].get("mbti", "ISTJ") + profile["other_info"]["gender"] = agent_info[i].get("gender", "other") + profile["other_info"]["age"] = agent_info[i].get("age", 30) + profile["other_info"]["country"] = agent_info[i].get("country", "Unknown") user_info = UserInfo( name=agent_info[i]["username"], diff --git a/frontend/src/views/SimulationView.vue b/frontend/src/views/SimulationView.vue index b6091a3f..c6b99053 100644 --- a/frontend/src/views/SimulationView.vue +++ b/frontend/src/views/SimulationView.vue @@ -71,7 +71,7 @@ import { useRoute, useRouter } from 'vue-router' import GraphPanel from '../components/GraphPanel.vue' import Step2EnvSetup from '../components/Step2EnvSetup.vue' import { getProject, getGraphData } from '../api/graph' -import { getSimulation, stopSimulation, getEnvStatus, closeSimulationEnv } from '../api/simulation' +import { getSimulation } from '../api/simulation' import LanguageSwitcher from '../components/LanguageSwitcher.vue' import { useI18n } from 'vue-i18n' @@ -177,68 +177,10 @@ const handleNextStep = (params = {}) => { // --- Data Logic --- -/** - * Check for and shut down any running simulation - * When the user returns from Step 3 to Step 2, we assume they want to exit the simulation - */ -const checkAndStopRunningSimulation = async () => { - if (!currentSimulationId.value) return - - try { - // First check whether the simulation environment is alive - const envStatusRes = await getEnvStatus({ simulation_id: currentSimulationId.value }) - - if (envStatusRes.success && envStatusRes.data?.env_alive) { - addLog(t('log.detectedSimEnvRunning')) - - // Try to gracefully shut down the simulation environment - try { - const closeRes = await closeSimulationEnv({ - simulation_id: currentSimulationId.value, - timeout: 10 // 10-second timeout - }) - - if (closeRes.success) { - addLog(t('log.simEnvClosed')) - } else { - addLog(t('log.closeSimEnvFailedWithError', { error: closeRes.error || t('common.unknownError') })) - // If graceful shutdown failed, try a forced stop - await forceStopSimulation() - } - } catch (closeErr) { - addLog(t('log.closeSimEnvException', { error: closeErr.message })) - // If graceful shutdown raised, try a forced stop - await forceStopSimulation() - } - } else { - // Environment is not running, but a process may still be alive — check status - const simRes = await getSimulation(currentSimulationId.value) - if (simRes.success && simRes.data?.status === 'running') { - addLog(t('log.detectedSimRunning')) - await forceStopSimulation() - } - } - } catch (err) { - // Failing to check env status shouldn't block the rest of the flow - console.warn('Failed to check simulation status:', err) - } -} - -/** - * Force-stop the simulation - */ -const forceStopSimulation = async () => { - try { - const stopRes = await stopSimulation({ simulation_id: currentSimulationId.value }) - if (stopRes.success) { - addLog(t('log.simForceStopSuccess')) - } else { - addLog(t('log.forceStopSimFailed', { error: stopRes.error || t('common.unknownError') })) - } - } catch (err) { - addLog(t('log.forceStopSimException', { error: err.message })) - } -} +// NOTE: checkAndStopRunningSimulation() / forceStopSimulation() were removed. +// They used to be called from onMounted, which killed in-progress simulations +// whenever a user opened the sim URL directly or refreshed Step 2. Step 3's +// handleGoBack() is the only place that should stop a sim now. const loadSimulationData = async () => { try { @@ -293,10 +235,14 @@ const refreshGraph = () => { onMounted(async () => { addLog(t('log.simViewInit')) - - // Check for and shut down any running simulation (when the user returns from Step 3) - await checkAndStopRunningSimulation() - + + // NOTE: We intentionally do NOT auto-stop running simulations on mount. + // Step 3's handleGoBack() is responsible for stopping the sim when the user + // explicitly clicks "Back" to return to Step 2. Previously this onMounted + // hook called checkAndStopRunningSimulation() unconditionally, which killed + // in-progress simulations whenever a user opened the sim URL directly or + // refreshed Step 2 (the "loading error" symptom). + // Load simulation data loadSimulationData() })