MOSAICModular System for Adaptive
Human–AI Collaboration

A Configurable Testbed for Human–AI TeamingIEEE SMC 2026 · Bellevue, WAgithub.com/iHuman-Lab/mosaic

Bennett Kwaku Dogbey · Elahe Oveisi · Hemanth Manjunatha

October 4, 2026

Meet the iHuman Lab

We study how people and intelligent systems work together.

Hemanth Manjunatha Principal Investigator

Elahe Oveisi PhD Candidate

Bennett Kwaku Dogbey PhD Student

Studying Human–AI Teaming

When an AI teammate advises people in dynamic, high-stakes tasks, researchers ask questions like these:

Trust and reliance

When do people follow AI advice, and when is that reliance appropriate?

Decisions under pressure

How do uncertainty and time constraints affect reliance?

Situation awareness

What happens when the person and AI have access to different information?

Cognitive state

How does AI advice affect attention and workload?

AI teammate design

Which teammate behaviors and communication strategies improve team performance?

Adaptive assistance

When should assistance change with task demands or human performance?

Answering these questions requires observing the interaction as it unfolds over time.

Why Do We Need a Configurable Testbed?

That takes four capabilities in one experiment. Existing tools rarely offer all four together.

01

Interactive decision making

People make a sequence of decisions, and each outcome changes what happens next.

03

Configurable task conditions

Researchers need to vary information access, uncertainty, time pressure, and the consequences of mistakes.

02

A customizable AI teammate

Researchers need to vary what the AI knows, how reliable it is, and when and how it communicates.

04

Synchronized data

Task events, human actions, AI advice, physiological signals, and sensor measurements must share a common timeline.

What Is MOSAIC?

MOSAIC is a modular testbed for human–AI teaming research. Configure its components for each study.

One room of the search-and-rescue task: lava, a victim, and doors.

01

Interactive task

Search and rescue, one decision at a time

A room shown with the room camera. The same room shown with the cone camera.

03

Configurable conditions

Map, view, timing, and scoring per study

MOSAIC

modular · configurable

The chat panel with the teammate's reply: Survivor to the north-east, about 3 steps away.

02

Customizable AI teammate

Scripted or LLM, with behavior you set

LSL

04

Synchronized data

Actions, advice, and signals on one clock

MOSAIC lets researchers vary the task and AI teammate, then record the interaction.

How MOSAIC Works

Each part is a separate module: the participant acts on the task, the AI teammate advises from task context, and LSL aligns every stream into one session record.

Human participant

Observes, decides, and acts.

MOSAIC task

Runs the task and the human-facing interface.

AI teammate

Interprets task context and provides advice.

physiological signals

task and interaction stream

Study sensors

Physiological data: EEG, eye tracking, pupil diameter

Lab Streaming Layer (LSL)

Aligns streams on a shared clock

Session data

Ready for analysis and replay

The Task: Search and Rescue

The whole search-and-rescue building: nine rooms with victims, lava, keys, and locked doors.

Mission

Search a multiroom building and rescue the real victims.

Real victims and decoys look alike, so an AI teammate’s advice can help or mislead.

Part Three plays the mission.

Search and rescue provides a controlled but consequential setting for studying human–AI decisions.

The Human–AI Teaming Loop

At every decision, the participant can act alone or ask for advice.

next decision

1 Observe

Read the room and the mission state.

2 Decide

Act alone, or ask the AI teammate.

act alone

3 Act

The participant always chooses the final action.

4 Outcome

The environment updates and the score changes.

Optional advice path

ask

use or ignore

Request advice

MOSAIC sends task context to the AI teammate.

advice

Evaluate advice

Compare it with what the participant can see.

Recorded throughout Task state · advice request · AI response · participant action · outcome

The central question is when and how AI advice changes human decisions.

Part Two

Install MOSAIC

Check the system, install MOSAIC, and run the mission

Why Install Experience Configure

QR code that opens the tutorial's Jupyter server

Companion notebook On the tutorial server: every setup command to copy, and the mission with nothing to install Sign in with your password, then open 02_mosaic_human_ai.ipynb jupyter.ihuman-lab.work

Before You Start

Check four things on your laptop before installing.

Python 3.10 or 3.11 check: python --version

Git Any recent version check: git --version

Terminal PowerShell, macOS Terminal, or a Linux shell

Display A local desktop: the game opens its own window

Installed for you by pip install -e .

Task

minigrid gymnasium numpy

Interface

pygame-ce pygame_gui

Study tools

pandas pyyaml python-dotenv

Nothing to install by hand: the next two slides set everything up.

No install, or prefer to copy and paste? The companion notebook is on the tutorial server: jupyter.ihuman-lab.work

Clone MOSAIC and Create the Environment

Everything in the tutorial runs from one repository and one environment.

git clone https://github.com/iHuman-Lab/mosaic.git
cd mosaic
python3 -m venv mosaic_env
source mosaic_env/bin/activate
git clone https://github.com/iHuman-Lab/mosaic.git
cd mosaic
python -m venv mosaic_env
.\mosaic_env\Scripts\Activate.ps1
git clone https://github.com/iHuman-Lab/mosaic.git
cd mosaic
conda create -n mosaic_env python=3.11 -y
conda activate mosaic_env

1 Download the code

Clones the MOSAIC repository

2 Enter the folder

Stay in mosaic for the whole tutorial

3 Create the environment

A private Python named mosaic_env

4 Activate it

Every later command runs inside it

Your prompt begins with (mosaic_env), and python --version reports 3.10 or 3.11.

Install MOSAIC

Five commands, run from the mosaic folder with mosaic_env active.

python -m pip install --upgrade pip
python -m pip install -e .
python -m pip uninstall -y pygame
python -m pip install --force-reinstall "pygame-ce>=2.5.2"
python -c "import mosaic.sar.env, mosaic.gui.main; print('MOSAIC ready')"

The check prints a pygame-ce banner, then MOSAIC ready. If it says cannot import name 'DIRECTION_LTR', rerun lines 3 and 4.

1 Update pip

Avoids old-installer errors

2 Install MOSAIC

-e means your Part Four edits apply without reinstalling

3–4 Switch to pygame-ce

The interface needs it; plain pygame hides it

5 Check it

Imports the task and the interface

Run the Included Mission

PYTHONPATH=src python -m experiment.main
$env:PYTHONPATH = "src"
python -m experiment.main

Window It opens fullscreen. F11 toggles a window (macOS: fn+F11, or set fullscreen: false in configs/experiment.yaml).

Move Arrow keys turn and walk the agent

Terminal Font warnings and connect_all failed lines are normal

The MOSAIC window at launch: a room crowded with victims and lava on the left; the mission, controls, and chat panels on the right

You should see this: the room on the left, the panels on the right.

First Checkpoint

Confirm

The window opened

Arrow keys move the agent

The information panel updates

It stays open for Part Three

Symptom Recovery
cannot import name 'DIRECTION_LTR' Rerun install lines 3 and 4 (pygame-ce)
No module named 'mosaic' Activate mosaic_env, rerun pip install -e .
No module named 'experiment' Run from the repo root with PYTHONPATH=src
python --version shows 3.9 or older, or 3.12 and newer Recreate mosaic_env with Python 3.10 or 3.11
No window, or No available video device Run locally, not over SSH

Everyone with a window open is ready for Part Three. Leave it open.

Part Three

Experience the SAR Mission

Play one mission and learn to read the screen

Why Install Experience Configure

The Search-and-Rescue Mission

Explore, tell real victims from decoys, and rescue them before time runs out.

The real interface: the agent rescues a real victim (green flash), then a decoy (red flash), then another real victim.

Explore

Move room to room. A locked door needs the key of the same color.

Identify

Both are red T shapes. A real victim’s stem is centered; a decoy’s is off-center.

Decide

A real victim earns a point, a decoy costs one, and the clock keeps running.

Every choice has a consequence you can see and score.

Game Interface

What each tile means, and where to read the mission.

MOSAIC interface caught mid-flash: a green edge vignette rings the game view after a rescue

Game viewport — the green flash is rescue feedback

Information panel — rescued, remaining, steps, inventory

Mission status and score — reward and the clock

Controls, always on screen

Chat panel — where AI teammate advice arrives

You the red triangle

Victim rescue with Tab

Decoy costs a point

Lava ends the mission

Door Space opens it

Locked door needs the key of its color

Key Tab picks it up

Victims and Decoys

Real victims and decoys are both red T shapes. Only real victims have health.

Four orientations of a symmetric real victim above four orientations of an offset decoy victim

REAL VICTIM DECOY

Real victim, full health bar Real victim, 60% health Real victim, 25% health Real victim, no health left

100% 60% 25% 0%

In the study runner, the white bar shows a victim’s health.

Symbols A real victim’s stem sits in the middle of the bar. A decoy’s is off-center.

Health In the lab’s study runner, health drains while a victim is in view, faster near lava, and the victim dies at zero. In today’s mission it stays full.

Rescue Stand next to a victim, face it, and press Tab. The score updates at once.

+1.0 Real victim rescued

−1.0 Decoy rescued

−2.0 Victim died · study runner

Real victims earn points and decoys cost them. Telling them apart is where advice matters.

Controls and Gameplay

Four actions. Each clip shows what its keys do.

The agent walks and turns inside a room while the arrow keycaps light up

Navigate

↑ Move forward one tile

←→ Turn to face a new direction

The agent walks up to a closed yellow door and Space opens it

Open

Space Open the door you are facing

Locked doors open only while you carry the matching key.

Tab picks up a key, then Tab rescues a victim and the view flashes green

Pick up or rescue

Tab Take the key, or rescue the victim, you are facing

Alt asks the teammate; the chat panel shows thinking, then the reply, and blinks

Request advice

Alt Ask the AI teammate

The reply appears in the chat panel. Today’s placeholder teammate answers “Currently, no commands are available.” Part Four gives it a real one.

Break · 15 minutes

Take a Break

Next · Part Four: Configure MOSAIC

Part Four

Configure MOSAIC

experiment/main.py in four parts: SAR, GUI, LLM, Sensing

Why Install Experience Configure

QR code that opens the tutorial's Jupyter server

Companion notebook On the tutorial server: every change in this part, as the edit and as a cell you can run Sign in with your password, then open 02_mosaic_human_ai.ipynb jupyter.ihuman-lab.work

Runtime Architecture

Four parts you can change, built on Gymnasium and MiniGrid.

↑ ← → Space Tab Alt

Human participant

Watches the screen and presses keys.

The MOSAIC interface

4.2 GUI mosaic.gui

Game view, panels, chat, and feedback.

The whole search-and-rescue building

4.1 SAR mosaic.sar

Rooms, victims, rules, and rewards.

physiological signals

context · advice

built on

LSL

4.4 Sensing experiment/

Gaze and EEG on the game’s clock, through LSL.

The chat panel showing the reply: Survivor to the north-east, about 3 steps away.

4.3 LLM mosaic.llm

Reads the task, replies in the chat.

obs, reward, terminated, truncated, info = env.step(action)

Gymnasium + MiniGrid

The step API and the grid world.

One File, Four Parts

Map 4.1 SAR 4.2 GUI 4.3 LLM 4.4 Sensing

src/experiment/main.py · lines 31–51

    env = build_sar_env(
        screen_size=800,
        num_rows=3,
        num_cols=3,
        room_size=10,
        victim_placer=LavaRiskVictimPlacer(
            num_real_victims=game_config.get("num_real_victims", 6),
            num_fake_victims=game_config.get("num_fake_victims", 12),
        ),
        lava_placer=SectorSpreadLavaPlacer(
            lava_per_room=game_config.get("lava_per_room", 8),
        ),
        locked_room_placer=LockedRoomPlacer(locked_room_prob=0.5),
        camera_strategy=AgentFOVCamera(),
    )
    env.reset()
    llm_client = build_llm_client(
        provider=config.get("provider", "dummy"), model=config.get("model")
    )
    gui = SAREnvGUI(env, config=config, llm_client=llm_client)
    gui.run()

4.1 SAR

The building, what fills it, the camera

4.2 GUI

The participant’s window

4.3 LLM

Who answers Alt

4.4 Sensing

What every step records

4.1 · SAR

Map 4.1 SAR 4.2 GUI 4.3 LLM 4.4 Sensing

Lines 31–44 build the task. Notebook: notebooks/02_mosaic_human_ai.ipynb

src/experiment/main.py lines 31–44

    env = build_sar_env(
        screen_size=800,
        num_rows=3,
        num_cols=3,
        room_size=10,
        victim_placer=LavaRiskVictimPlacer(
            num_real_victims=game_config.get("num_real_victims", 6),
            num_fake_victims=game_config.get("num_fake_victims", 12),
        ),
        lava_placer=SectorSpreadLavaPlacer(
            lava_per_room=game_config.get("lava_per_room", 8),
        ),
        locked_room_placer=LockedRoomPlacer(locked_room_prob=0.5),
        camera_strategy=AgentFOVCamera(),

Three things to change

1 The building Notebook

YAML counts · num_rows · locked_room_prob

How big, crowded, and locked

2 Camera Next

camera_strategy=

What the participant sees

3 Rewards Try it

action=RescueAction(...)

What a mistake costs

What the Participant Sees

Map 4.1 SAR 4.2 GUI 4.3 LLM 4.4 Sensing

Line 44 sets the camera. One walk through a door, three cameras:

EdgeFollowCamera: an eight-by-eight window that scrolls with the agent and shows parts of neighboring rooms

Edge-following

EdgeFollowCamera · scrolls with the agent

AgentFOVCamera: the view is always exactly the agent's room and switches rooms at the door

Whole room

AgentFOVCamera · the default

AgentConeCamera: only the forward wedge of the room in front of the agent is visible

Forward cone

AgentConeCamera · only what is ahead

Agent same tile in all three   ·   Room the agent’s room

Same task. The camera sets what the participant can see.

Rescue Rewards

Map 4.1 SAR 4.2 GUI 4.3 LLM 4.4 Sensing

Try it Make rescuing a decoy cost 5 points instead of 1.

src/experiment/main.py an import, then inside build_sar_env

+from mosaic.sar.actions import RescueAction, RescueRewards
@@ inside build_sar_env(...) @@
         camera_strategy=AgentFOVCamera(),
+        action=RescueAction(
+            rewards=RescueRewards(fake_victim=-5.0)),

Run Relaunch and rescue a decoy.

What will the reward read?

Reward panel reading minus 1.0 after a decoy.

Before · the default

Reward panel reading minus 5.0 after a decoy.

After · fake_victim=-5.0

4.2 · GUI

Map 4.1 SAR 4.2 GUI 4.3 LLM 4.4 Sensing

Lines 50–51 open the participant’s window.

src/experiment/main.py lines 50–51

    gui = SAREnvGUI(env, config=config, llm_client=llm_client)
    gui.run()

The MOSAIC window: the game view, the mission panel, and the chat panel.

Three things to change

1 Feedback flash Demo · next

vignette=EdgeVignette(...)

How a mistake feels

2 Info panel Appendix

info_panel=

What progress is shown

3 Time limit Appendix

max_time: (YAML)

How much time pressure

Feedback Flashes

Map 4.1 SAR 4.2 GUI 4.3 LLM 4.4 Sensing

Presenter demo Make the red flash after a decoy stronger and longer.

src/experiment/main.py an import, then the gui = line

+from mosaic.gui.feedback import EdgeVignette, VignetteStyle
@@ further down @@
-    gui = SAREnvGUI(env, config=config, llm_client=llm_client)
+    gui = SAREnvGUI(env, config=config, llm_client=llm_client,
+        vignette=EdgeVignette(800, styles={"wrong_victim":
+            VignetteStyle((200, 0, 0), 200, 1.5, "fade")}))

Run Relaunch and rescue a decoy.

How will the mistake feel different?

A faint red edge: the default decoy flash, already fading.

Before · default, 0.6 s

A strong red edge: the tuned decoy flash.

After · stronger, 1.5 s

4.3 · LLM

Map 4.1 SAR 4.2 GUI 4.3 LLM 4.4 Sensing

Lines 47–49 pick the AI teammate. Today it is the dummy placeholder.

src/experiment/main.py lines 47–49

    llm_client = build_llm_client(
        provider=config.get("provider", "dummy"), model=config.get("model")
    )

Chat panel: Currently, no commands are available.

Today, after Alt

Three things to change

1 Keyless teammate Try it · next

llm_client=ReliableTeammate(0.7)

How reliable the advice is

2 Real LLM On your own

provider: in the YAML, key in .env

Who answers Alt

3 Prompt Appendix

prompt_builder=

What the teammate is told

An AI Teammate Without a Key

Map 4.1 SAR 4.2 GUI 4.3 LLM 4.4 Sensing

Try it Swap in a teammate that reads the map and is right 7 times in 10.

Get advisor.py into src/experiment/, from
github.com/Bkdogbey/mosaic-smc-tutorial · presentation/labs/

src/experiment/main.py imports, then the gui = line

+import json
+from .advisor import ReliableTeammate
@@ further down @@
-    gui = SAREnvGUI(env, config=config, llm_client=llm_client)
+    gui = SAREnvGUI(env, config=config,
+                    llm_client=ReliableTeammate(0.7),
+                    prompt_builder=json.dumps)

Run Relaunch and press Alt.

Is the advice always right?

Chat panel: Currently, no commands are available.

Before · the dummy placeholder

Chat panel: Survivor to the north-east, about 3 steps away.

After · ReliableTeammate, grounded in the map

Connect a Real LLM

Map 4.1 SAR 4.2 GUI 4.3 LLM 4.4 Sensing

On your own With your own key: two packages, one key, two lines of YAML.

terminal once, with mosaic_env active

python -m pip install llama-index-llms-openai tabulate

.env a new file in the mosaic folder

OPENAI_API_KEY=<your key>

configs/experiment.yaml new top-level lines

+provider: openai
+model: gpt-4o-mini

Run Relaunch and press Alt.

How it answers

1 Prompt The game state, as rules and a table

2 Model One reply, a few seconds later

3 Chat The participant decides whether to follow it

4.4 · Sensing

Map 4.1 SAR 4.2 GUI 4.3 LLM 4.4 Sensing

Each step produces an observation. The study runner streams it with sensors on one clock.

src/experiment/main.py line 46

    env.reset()
obs → agent_x, agent_y, direction, grid,
      saved_victims, remaining_victims,
      victim_health, step_count, …
    + action, reward, llm_response
    → study runner: one JSON line per frame on LSL

Three things to change

1 Recorded fields Notebook

print(sorted(obs))

What each step holds

2 Custom fields

observation= in build_sar_env

Add or drop fields

3 Sensors Demo · next

register_sensor(...)

Gaze or EEG on the game’s clock

Live Demo: Eye Tracking

Map 4.1 SAR 4.2 GUI 4.3 LLM 4.4 Sensing

Presenter demo A Tobii eye tracker on the lab’s study runner, while a volunteer plays.

src/experiment/eye_demo.py the study runner

     experiment = Experiment(config)
+    experiment.register_sensor(
+        name="TobiiEyeTracker", sensor_cls=TobiiEyeTracker,
+        sensor_config={})
+    experiment.calibrate_sensor(
+        "TobiiEyeTracker", screen=config["display"],
+        fullscreen=config["fullscreen"])
     experiment.add_task(task_cls=SARGameDemo, ...)

What lands next to the game data?

Watch for

1 Calibration Five dots

2 Play A short mission

3 Two streams, one clock TobiiEyeTracker (gaze, 60 Hz) and SARGame (game state, every frame), side by side in LabRecorder

The Synchronized Record

Map 4.1 SAR 4.2 GUI 4.3 LLM 4.4 Sensing

One recorded mission gaze and game on the same clock

Fixations drawn on the screen's three areas, almost all on the game view, above a timeline of three rescues and a strip showing the gaze stayed on the game view, with gaps where the tracker lost the eyes.

What to look for

Where Which part of the screen holds the gaze?

When What do the eyes do in the second after a rescue?

Advice Where does gaze go when the teammate replies?

Gaze and game share one clock, so every look lines up with what happened.

Where to Go Next

The parts you changed today, and where they live.

src/experiment/ One study built on the package. Copy this folder to start yours.

main.py

placers.py

llm.py

pacing.py

game.py

replay.py

sensors/

API build_llm_client · LavaRiskVictimPlacer · SARGameTrial

composes

src/mosaic/core/ 4.1

Level generation and cameras

level.py

placers.py

camera.py

API Placer · CameraStrategy

src/mosaic/sar/ 4.1 4.4

The search-and-rescue task

env.py

placers.py

actions.py

objects.py

observations.py

API build_sar_env · RescueAction

src/mosaic/gui/ 4.2

What the participant sees

main.py

user.py

chat.py

info.py

feedback.py

API SAREnvGUI · EdgeVignette

src/mosaic/llm/ 4.3

The teammate contract

client.py

process_prompts.py

prompts.yaml

pathfinding.py

parser.py

API LLMClient · ask

MOSAIC in a Study

What you configured today, put to work in a study of people and an LLM teammate.

LLM-Mediated Human-AI Interaction in Search and Rescue: Impact of Expertise on Attentional Allocation Elahe Oveisi · Hemanth Manjunatha

Task The search-and-rescue mission, with victims whose health runs down.

Teammate Two LLM teammates and a no-LLM baseline, three trials per participant.

Sensing A Tobii eye tracker and the game on one clock, through LSL.

Question Does guidance change where people look, and does expertise matter?

QR code that opens the slides of the paper's talk

Monday, October 5 · 14:00–14:15 Grand C · Session MoA10 Scan for the talk’s slides

Come and hear what they found: Monday at 14:00, Grand C.

Thanks!

Questions?

Appendix

More changes and reference

Each change starts from the stock files

Locked Rooms

Set the chance that a room is locked: 0.5 locks about half, 0.9 most.

src/experiment/main.py line 43

-locked_room_placer=LockedRoomPlacer(locked_room_prob=0.5),
+locked_room_placer=LockedRoomPlacer(locked_room_prob=0.9),

Run Relaunch and walk to the nearest door.

How much of the building can you still reach?

The building at locked_room_prob 0.5, with four locked doors ringed in orange.

Before · 0.5: 4 locked doors

The building at locked_room_prob 0.9, with eight locked doors ringed in orange.

After · 0.9: 8 locked doors

Time Limit

Give the mission 2 minutes instead of 5.

configs/experiment.yaml a new top-level line, not under game:

 display: &display 0
 fullscreen: &fullscreen true
+max_time: 2

Run Relaunch and watch the timer.

How will you play with only 2 minutes?

The timer at the start reads 5:00.

Before · the default

The timer at the start reads 2:00.

After · max_time: 2

At 0:00 the timer stops; the mission keeps going.

The Info Panel

Hide how many victims remain.

Get panel.py into src/experiment/, from
github.com/Bkdogbey/mosaic-smc-tutorial · presentation/labs/

src/experiment/main.py an import, then the gui = line

+from .panel import NoProgressPanel
@@ further down @@
-    gui = SAREnvGUI(env, config=config, llm_client=llm_client)
+    gui = SAREnvGUI(env, config=config, llm_client=llm_client,
+                    info_panel=NoProgressPanel(800, 400, 400))

Run Relaunch and look at the mission box.

What does the participant no longer know?

Mission box showing rescued and remaining counts.

Before · rescued and remaining

Mission box showing only the rescued count.

After · no remaining count

What the Teammate Is Told

Before trusting its advice, look at what it is told.

Every Alt sends

1 Rules Rescue real survivors, nearest first, never through lava

2 Every real victim Even ones out of your view

3 No decoys Left out, so it never points at one

Change it SAREnvGUI(..., prompt_builder=...)

The start of one prompt

Agent: room (0,1) facing West | …

Object       Direction  Visible  PathLength
Victim4      East       Yes      9
Victim2      South      Yes      0
RedKey1      East       Yes      5
PurpleKey1   East       Yes      6
PurpleDoor1  South      Yes      34
RedDoor1     East       No       10