A web-based chatbot implemented as proof-of-concept that leverages a LLM-based multi-agent architecture for the generation of health behavior persona data. By defining input variables based on a description in natural language from a domain researcher, health behavior personas are created. These variables are used to create a SMT-solver problem based on the constraints in the variables, this generates event data for the specified users locally.
The application uses:
- a FastAPI backend
- a Streamlit frontend
- PostgreSQL user-data storage
- a per-user
hb_agentcontainer for the multi-agent workflow
The multi-agent architecture is based on expert agents inspired by the CoALA paper. This includes RAG (semantic memory), conversation memory (episodic memory), and expert prompts (procedural memory). ChromaDB is used for memory storage, LangChain/LangGraph are used for agent orchestration, and the supervisor is implemented as a ReAct agent.
- Docker
- OpenAI key
- Local compute capacity for the Z3-based SMT solver
- Clone the repository
- Build the local agent image first with
docker compose up --build hb_agent - Then launch the shared services with
docker compose up --build - FastAPI is exposed on host port
8001to avoid common local conflicts on8000 - The compose file pins PostgreSQL to major version
17so the persisted Docker volume is not broken bypostgres:latestupgrades hb_agentis not used as one shared always-on Compose service during normal usage. After login, Streamlit starts a per-user container such ashb_agent_<username>- Initial ChromaDB setup for general semantic memory still has to be done manually. Embed documents under:
general_environmentalgeneral_eventgeneral_analytical
- Login screen to access the chatbot
- Register page to add username, password, and OpenAI key
- After login, a user-specific
hb_agentcontainer is started with that user’s OpenAI key - The chatbot interface supports:
- chatting with the supervisor
- uploading PDFs per expert for semantic memory / RAG
- updating episodic memory
- clearing runtime message history without writing a reflection
- inspecting stored variables
- deleting one stored variable or clearing all stored variables
- downloading generated data
The full flow is visualized below.

- The multi-agent network is based on one ReAct supervisor and three experts:
- analytical expert: extracts relations between events and describes these relations using LTL, stored as
ltl_expressions - environmental expert: extracts environmental and demographic information, stored as
constant_persona_features - event expert: extracts event definitions and constraints, stored as
eventironmental_data
- analytical expert: extracts relations between events and describes these relations using LTL, stored as
- The expert prompts used to initialize these experts and the supervisor are available in
HB_Agent/Set_up/Templates - Additional experts can be added, but the supervisor prompt and
HB_Agent/main.pymust be adjusted
- Each user has at most one stored value for each required variable:
constant_persona_featureseventironmental_dataltl_expressions
- If an expert generates a new valid version of one variable, it overwrites the previous stored version for that same user
- The supervisor uses stored variables as the source of truth
- If a variable is already stored and complete, the supervisor should not query that expert again unless the user explicitly asks for a revision
- If a variable is missing or incomplete, the supervisor only queries the relevant expert
- The full stored variable values are shown in the sidebar inspection panel for the user
- The supervisor has a storage inspection tool that only returns a compact status per required variable
- For each required variable, the tool returns:
checkmarkif the variable is present and complete- a follow-up question or issue if the variable is missing or incomplete
- The full variable bodies are not returned through this supervisor check path
- This keeps inter-agent communication shorter and reduces token use
- Runtime message history is separate from the persistent variable store
- The supervisor keeps its own conversation history
- Each expert keeps its own runtime message history inside its conversation handler
Update Memoryreflects the current runtime histories into episodic memory and then clears:- environmental expert runtime history
- event expert runtime history
- analytical expert runtime history
- supervisor runtime history
Clear Message Historyclears those runtime histories without storing a reflection first
- The event data generation folder in
HB_Agentuses the three required variables generated by the multi-agent architecture - In the current flow, these variables are read directly from per-user stored variable state when
run_data_generationis called - For constant persona generation, the following elements are implemented:
- age_group
- age
- gender
- education_level
- occupation_status
- marital_status
- field_of_study
- housing_type
- time_window
- number_of_subjects
- horizon
- For the analyst agent, the following inter-event constraints are implemented:
- No Overlap:
G ¬(A ∧ B) - Implication (A implies B in future):
G (A → F B)on a daily basis - Event A must overlap with at least one event B:
G (A → F (A ∧ B))
- No Overlap:
- Event constraints implemented:
per_event_duration: { min, max, unit }total_event_duration_daily: { scale, min, max, unit }total_event_episodes_daily: { scale, min, max, unit }temporal_patterns- seasonality
- time window or day:
morning,afternoon,evening,night,weekday,weekend,day_part - direction:
increasingordecreasing - amount: numeric percentage
- within which window or day: single value or list
- time window or day:
- trend
- scale: season
- direction:
increasingordecreasing - amount: integer number of events
- within: number of days
- start: start day
- end: end day
- fix
- used to fix an event to one time window, for example only night
- seasonality
- The solver builds a model for each day, and events that span the next day are carried over as spillover
- LTL constraints are enforced on a per-day basis
- Daily event counts are supplied according to trends combined with randomness
- A compliance check is added to the generated data. This is not a strict pass/fail test but indicates the actual realized percentages per window/day
- Visualization plots are added to the generated data
- Outputs are downloaded through the ZIP implementation
The algorithm is visualized below.

- The included test case is based on the study of Paciorkowski et al.
- Four different smoking cessation profiles are identified in this study
- The smoking behavior is described in a natural language prompt, adjusted to a horizon of 90 days to limit computational strain and augmented with additional events and inter-event relations
- These inter-event relations are stored in a PDF available at
HB_Agent/Set_up/Semantic_memoryand supplied to the semantic memory of the analytical agent - The results of these prompts and interaction logs are available in:
Long_term_quittersPersistent_smokersRepeated_try_and_failsShort_term_returner
- Paciorkowski, M., Baty, F., Pohle, S., Bürki, E., Brutsche, M. (2022). Identification of smoking cessation phenotypes as a basis for individualized counseling: An explorative real-world cohort study. Tobacco Induced Diseases, 20(September), 81. https://doi.org/10.18332/tid/152546
- Sumers, T. R., Yao, S., Narasimhan, K., & Griffiths, T. L. (2024). Cognitive architectures for language agents (Version 3) [Preprint]. arXiv. https://arxiv.org/abs/2309.02427
Everything runs inside the test container, no local Python. The repo is mounted at /app,
so code and config edits are picked up on the next run with no rebuild; rebuild only when a
dependency changes (for example when pycvcqv was added for the fidelity heterogeneity charts).
# 1. Build the test image once (installs z3, the agent stack, pytest-cov, and pycvcqv)
docker compose -f docker-compose.test.yml build tests
# 2. Run the unit suite with branch coverage (term-missing report + HTML in ./htmlcov)
docker compose -f docker-compose.test.yml run --rm tests
# 3. Generate the full LifeSnaps population (60 persons over 12 weeks) from the fitted config
docker compose -f docker-compose.test.yml run --rm -e PYTHONPATH=/app/HB_Agent tests \
python -u -m event_data_generation.Model_builder.generate_from_config \
--config /app/experiments/lifesnaps_personas --out /app/outputs/lifesnaps
# 4. Evaluate fidelity of the synthetic run against the observed LifeSnaps log
docker compose -f docker-compose.test.yml run --rm -e PYTHONPATH=/app/HB_Agent tests \
python -u -m event_data_generation.Check_data.evaluate_fidelity \
--real /app/experiments/lifesnaps_personas/observed_lifesnaps_personas_event_log.csv \
--synthetic-json /app/outputs/lifesnaps \
--out /app/outputs/fidelity_lifesnaps \
--figure /app/outputs/fidelity_lifesnaps/lifesnaps_real_vs_synthetic.pngOutputs land under ./outputs (and ./htmlcov), which are mounted from the host:
outputs/lifesnaps/— per-person JSON, the combined event-log CSV, per-persona charts, andsummary_report.txt(person and day counts, unsatisfiable days, and overlap/rule violations).outputs/fidelity_lifesnaps/—metrics.csv(per persona per event: permutation p and Benjamini-Hochberg q, RMSE, DTW, MMD, KL and JS divergence, lag-1 autocorrelation, Lin's concordance, Hedges g effect size, and the CQV of both streams, all with confidence intervals), the per-event panels, and the paired-count, paired-duration, effect-size-forest, and CQV-heterogeneity figures.htmlcov/index.html— the branch-coverage report.
Run a single test file with, for example,
docker compose -f docker-compose.test.yml run --rm tests pytest HB_Agent/tests/test_solver.py.
The direct (no Streamlit) path turns each persona's natural-language prompt (experiments/<Persona>/Prompt_*.txt) into the four YAML files with one OpenAI call per expert (environmental, event, analytical), each grounded by its Chroma semantic-memory collection (the LTL constraints PDF for the analytical expert). It needs OPENAI_API_KEY in .env (read by config.py).
# Generate the four YAML files for each smoking persona
docker compose -f docker-compose.test.yml run --rm -e PYTHONPATH=/app/HB_Agent tests \
python -m agents.generate_configs_live \
--personas Persistent_smokers Long_term_quitters Repeated_try_and_fails Short_term_returnerEach persona's four files land in experiments/<Persona>/generated_config/ (environment.yaml, event_config.yaml, persona_config.yaml, temporal_relation_rules.yaml). Flags: --personas selects which persona directories to run (default: the four smoking personas above); --no-memory skips the Chroma semantic grounding; --experiments-dir points elsewhere.
Then generate the raw event data for every smoking persona by running each generated config through the same generate_from_config entrypoint as the LifeSnaps pipeline:
# Simulate raw event data (per-person JSON, event-log CSV, charts, report) per smoking persona
for p in Persistent_smokers Long_term_quitters Repeated_try_and_fails Short_term_returner; do
docker compose -f docker-compose.test.yml run --rm -e PYTHONPATH=/app/HB_Agent tests \
python -u -m event_data_generation.Model_builder.generate_from_config \
--config /app/experiments/$p/generated_config \
--out /app/outputs/${p}_generated
doneEach run writes per-person JSON, the combined event-log CSV, per-persona charts, and summary_report.txt under outputs/<persona>_generated/.