fix: prevent frontend from killing in-progress simulations on view mount

- frontend/src/views/SimulationView.vue: remove onMounted auto-stop of running
  simulations. Step 3's handleGoBack() already stops the sim when the user
  explicitly clicks 'Back', so onMounted only killed sims when users opened
  the sim URL directly or refreshed Step 2 (the 'loading error' symptom).
- backend/scripts/run_parallel_simulation.py: lower OASIS semaphore from 30
  to 8 to fit the 8GB host's memory budget.
- backend/vendor/camel-oasis/oasis/social_agent/agents_generator.py: add
  .get() defaults for mbti/gender/age/country to handle sparse agent
  profiles (KeyError: 'mbti' bug).
This commit is contained in:
profikid 2026-06-10 10:13:02 +00:00
parent 12669495bb
commit 8dfa7645e6
3 changed files with 37 additions and 75 deletions

View File

@ -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()

View File

@ -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"],

View File

@ -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()
})