A benchmark framework for evaluating AI agents on reproducible mobile app tasks. DigiWorld provides scenarios across 15 Android applications, with automated state management, template-driven data generation, and programmatic verification of task completion.
| Area | What it covers | Where |
|---|---|---|
| General / Framework | Installing DigiWorld, the CLI, the Python API, scenario authoring, and architecture — everything about the benchmark framework itself. | This README (sections below) |
| Mock Applications | The 15 React Native Android apps under test: building, running, and per-app schemas and documentation. | apps/README.md |
| Evaluation | Running a vision-language agent against the apps on an emulator and scoring each task with the built-in verifiers. | digiworld_eval/README.md |
| Container | Running the emulator, the apps and the eval server inside Docker or Podman instead of installing them locally. | docker/README.md |
| Scenario Explorer Demo | Interactive web UI for browsing and validating scenarios. | digiworld/demos/scenario_explorer_demo/README.md |
| ADB Interaction Layer | Low-level library that drives the emulator over ADB. | digiworld/docs/api/adb_readme.md |
Jump to a section in this README: Installation · Quick Start · CLI Reference · Data · Evaluation · Python API · Architecture · Testing · Troubleshooting
- Python 3.12
- Git LFS (state data and media assets are stored with LFS)
- Android Debug Bridge (ADB)
- A running Android emulator accessible via ADB
# 1. Create an isolated environment (Python 3.12)
conda create -n digiworld python=3.12 -y
conda activate digiworld
# venv alternative: python3.12 -m venv .venv && source .venv/bin/activate
# 2. Install Git LFS and pull large files (state data, media assets)
git lfs install
git lfs pull
# 3. Install the digiworld package and its dependencies (editable)
cd digiworld
pip install -r requirements.txtThis installs both the digiworld package and its dependency andojo-agent (the ADB interaction layer) in editable mode.
Verify the installation:
digiworld --version
digiworld listMost digiworld commands — and the eval harness — drive a
running Android emulator over ADB. Start an emulator, then confirm ADB can see it:
adb version # ADB is installed and on PATH
adb devices # should list e.g. "emulator-5554 device"If no device is listed, boot one and wait for it to come online (the emulator
binary ships with the Android SDK; devices are created in Android Studio's Device
Manager):
emulator -list-avds # show available virtual devices
emulator -avd <avd_name> & # boot one in the background
adb wait-for-device && adb devices # wait until it reports "device"If a device shows as offline/unauthorized or goes missing, restart the ADB server:
adb kill-server && adb start-serverThe unit suite runs without an emulator. Install the test extra (adds pytest plus
the dependencies the scenario registry imports), then run it from the repo root:
cd digiworld
pip install -e ".[test]"
cd ..
python -m pytest tests/unit/ -q# List all scenarios across all apps
digiworld list
# Filter by app
digiworld list --app email
# Show detailed info including instances
digiworld list --verbose --instances
# Show aggregate statistics
digiworld statsA scenario lifecycle has three phases: instantiate, reset, and verify.
import digiworld
from digiworld.scenarios.scenario_registry import scenario_registry
# Instantiate a pre-configured test instance
scenario = scenario_registry.get_instance(
app_name='email',
task_name='Send email to',
instance_name='personal_fjohnson_4',
base_path=digiworld.get_state_data_path()
)
# Reset the app to a clean, reproducible initial state.
# This selects a compatible profile, loads state data onto the emulator,
# and resolves any mockdata templates.
scenario.reset_initial_state()
# --- Agent or human performs the task here ---
# Verify whether the task was completed by inspecting the final app state.
# Pass a list of state paths (or session IDs); only the final state is checked.
metrics = scenario.verify_trajectory(["path/to/final/state"])
print(f"Task completed: {metrics['task_completed']}") # 1.0 or 0.0digiworld info email send_email_to personal_fjohnson_4digiworld export-tasks| Command | Description |
|---|---|
digiworld create-variants |
Create theme variants for assembled profiles |
digiworld list |
List all scenarios (use --app, --verbose, --instances) |
digiworld stats |
Show scenario/instance counts and coverage statistics |
digiworld info <app> <task> <instance> |
Show details for a specific instance |
digiworld export-tasks |
Export all tasks to tasks_list.jsonl |
digiworld update-scenarios |
Regenerate scenario_list.json after adding/modifying scenarios |
digiworld list-states |
Show state data summary |
digiworld clean-states |
Clean non-default states (--dry-run to preview) |
digiworld --version |
Print version |
DigiWorld ships with all profile mock data pre-generated and committed to the
repository, so no generation step is required. Scenario instances (parameters and
per-instance mockdata) are likewise pre-generated and committed under each
scenario's instances/ directory.
Note: The LLM-based
generate-profilescommand has been removed along with the generation pipeline. Profile data is now sourced entirely from the committedstate_data/directory.
Each app has ~10 named profiles (e.g., default, high_balance, new_user) that
vary parameters like data volume, balances, and statuses. Each profile directory
contains its mockdata, rootstore.json, SQLite database, and theme, and lives under:
digiworld/digiworld/state_data/<bundle_id>/<profile>/
Create theme variants for assembled profiles (variants share the same database and mockdata, differing only by theme):
digiworld create-variants --app payment # All profiles for one app
digiworld create-variants --app payment --profile big_spender
digiworld create-variants --list # List available themesTo inspect what data is present:
digiworld list-states
digiworld list --verboseDigiWorld covers 15 apps across 7 domains:
| Domain | Apps |
|---|---|
| Communication | Email, Message |
| Financial | Payment, Banking |
| Shopping | Ecommerce, Qwikshop, Auction |
| Entertainment | Music, Video |
| On-Demand | Eats, Ryde |
| Travel | Flight Booking, Transit |
| Smart Living | Smart Home, Parking |
To see the full list of scenarios and instances:
digiworld list --verbose --instances
digiworld statsFor details on the individual mock applications (React Native setup, building APKs, app-level documentation), see apps/README.md.
The digiworld_eval/ directory is a standalone evaluation harness: it runs a
vision-language agent against these apps on an emulator and scores each task with the
built-in verifiers. It drives the emulator over a small HTTP bridge and calls models
via litellm (GPT / Claude / Gemini). Setup and run instructions
are in digiworld_eval/README.md.
There are two ways to provide the emulator the harness drives:
- A local emulator — you install the Android SDK, boot an emulator and run the bridge server yourself. Works on Linux, macOS and Windows. See digiworld_eval/README.md.
- A container — Docker or Podman runs the emulator, the pre-installed apps and the bridge server behind one port, and the harness can start and stop containers itself for parallel evaluation. Needs a Linux host with KVM. See docker/README.md.
Both expose the same HTTP API, so a config written for one runs against the other.
scenario_registry -- Singleton registry that auto-discovers all scenarios at import time.
from digiworld.scenarios.scenario_registry import scenario_registry
# List all (app_name, task_name) pairs
scenario_registry.get_scenario_list()
# List all (app_name, task_name, instance_name) triples
scenario_registry.get_instance_list()
# Get a scenario with custom parameters
scenario_registry.get_scenario(
app_name='payment',
task_name='Change pin to',
base_path=digiworld.get_state_data_path(),
new_pin='5678'
)
# Get a pre-configured instance
scenario_registry.get_instance(
app_name='email',
task_name='Send email to',
instance_name='personal_fjohnson_4',
base_path=digiworld.get_state_data_path()
)
# Get instance configuration dict
scenario_registry.get_instance_config('email', 'Send email to', 'personal_fjohnson_4')Scenario -- Abstract base class. All scenarios expose:
| Method | Description |
|---|---|
reset_initial_state() |
Load a clean state onto the emulator and return the selected profile name |
verify_trajectory(state_paths) |
Check if the task was completed; returns {'task_completed': float} |
get_task_description() |
Human-readable task description for the agent |
get_agent_context() |
Full context dict for the agent (task description, app info, etc.) |
compare_database_records(db1, db2, query, params) |
Compare two database snapshots |
digiworld module-level helpers:
import digiworld
digiworld.get_state_data_path() # Path to state_data directory
digiworld.get_project_root() # Path to digiworld/ project root
digiworld.get_package_dir() # Path to digiworld/digiworld/ package dir
digiworld.get_apk_versions() # Min compatible APK versions from lockfile
digiworld.get_bundle_id('email') # Bundle ID for an app name
digiworld.get_all_app_names() # All registered app names| Variable | Description |
|---|---|
DIGIWORLD_STATE_DATA |
Override the default state_data directory. If the path does not exist, the built-in state_data is copied there automatically. |
DIGIWORLD_INCLUDE_THEME_VARIANTS |
Set to false to exclude theme variant profiles during reset_initial_state(). Defaults to true. |
1. Discovery scenario_registry scans for scenario_config.json files
and registers (app_name, task_name) -> ScenarioClass mappings
2. Instantiation scenario_registry.get_instance() creates a Scenario object,
loads app/scenario/instance configs, initializes helpers
3. Reset reset_initial_state() selects a compatible profile, loads
state data via ADB, resolves mockdata templates
4. Execution External: agent or human performs the task on the emulator
5. Verification verify_trajectory() checks the final app state against
the scenario's completion criteria
The Scenario base class delegates to specialized helper services rather than implementing everything directly:
| Helper | Responsibility |
|---|---|
ConfigLoader |
Loads and validates app, scenario, and instance configuration files |
StateManager |
Database connections, queries, state path resolution, snapshot comparison |
TemplateResolver |
Resolves {{placeholder}} templates in mockdata and context |
MockdataHandler |
Loads templated mockdata JSON and inserts it into databases |
MockdataValidator |
Validates mockdata JSON files against database schemas |
ContextExtractor |
Extracts user context (email, ID, etc.) from databases and JSON stores |
PositioningService |
Generates timestamps positioned relative to existing database content |
AnswerMatchers |
Flexible comparison functions for verification (numeric, date, duration, etc.) |
Constraints |
Feasibility constraint types (EntityExistsConstraint, DataVolumeConstraint, BalanceConstraint) |
Each scenario lives in its own directory under digiworld/digiworld/scenarios/scenarios/<app_name>/<task_dir>/ and consists of several files:
| File | Purpose |
|---|---|
scenario_config.json |
Declares the task name, app, parameters, context fields, and mockdata requirements |
scenario.py |
Implements the verification logic |
instances/ |
Pre-generated, committed test instances (parameters and per-instance mockdata) |
feasibility.py |
Declares constraints on which profiles are compatible (when the scenario depends on pre-existing data) |
__init__.py |
Minimal or empty |
Before writing code, decide:
- Mockdata injection: Set
additional_mockdata: truewhen the scenario operates on data that may not exist in all profiles (a specific video title, transaction, etc.). Omit it when the scenario works with data guaranteed to exist across profiles. - Parameters vs. context fields: Parameters are
<param>placeholders in the task name that becomeself.<param>at runtime. Context fields are runtime data extracted from the profile's DB/rootstore that the agent needs (e.g.,current_user_pin). Only list fields the app'sContextExtractorcan resolve. - Scenario context: When the agent needs profile-dependent information (e.g., a user PIN that varies per profile), use
scenario_contextwith template values like"scenario_context": {"userPin": "{{current_user_pin}}"}. Any template used must have its context field declared incontext_fields. - Feasibility constraints: Create
feasibility.pywhenever the scenario has preconditions on the profile's existing data. Available constraint types fromdigiworld.scenarios.constraints:EntityExistsConstraint,DataVolumeConstraint,BalanceConstraint. - Non-triviality: After feasibility filtering, profiles where the task is already completed in the initial state are excluded. This filtering is already baked into the committed instances and requires no additional code in the scenario.
scenario_config.json:
{
"task_name": "Send $<amount> to <nickname>",
"app_name": "payment",
"scenario_class": "SendPaymentToNicknameScenario",
"parameters": ["amount", "nickname"],
"context_fields": ["current_user_pin"],
"description": "Send a payment to a contact by nickname",
"additional_mockdata": true
}task_nameuses<param>placeholders that must exactly match theparameterslist.scenario_classmust exactly match the class name inscenario.py(case-sensitive).app_namemust be a valid key inAPP_REGISTRY.- Optional
"time"field: ISO 8601 string to set the device clock for time-sensitive tasks.
scenario.py:
from digiworld.scenarios.scenarios.payment.base_scenario import PaymentScenario
from digiworld.scenarios.verification import ComposableScenario
class SendPaymentToNicknameScenario(PaymentScenario, ComposableScenario):
def _get_checks(self, state_path: str) -> list:
query = """
SELECT amount, recipientNickname, status
FROM transactions
WHERE recipientNickname = ? AND status = 'completed'
"""
_, _, new_txns = self.compare_database_records(
self.initial_state_path, state_path, query,
(self.nickname,)
)
if not new_txns:
raise ValueError(f"No completed transaction found for {self.nickname}")
txn = list(new_txns)[0]
return [
("amount", float(txn[0]), float(self.amount)),
("recipient", txn[1], self.nickname),
]Key rules:
- Use parameterized SQL (
?placeholders), never string interpolation. - Raise
ValueErrorwhen expected data is not found -- never returnFalsesilently. - Class inherits from
(AppBaseScenario, ComposableScenario)with the app base first. self.<param>attribute names match theparameterslist exactly.
feasibility.py (when the scenario depends on pre-existing profile data):
from digiworld.scenarios.constraints import DataVolumeConstraint
CONSTRAINTS = [
DataVolumeConstraint(
table="transactions",
min_count=2,
user_filter=True,
),
]- Verify that
scenario.pyimports resolve correctly and the scenario appears indigiworld list. - Run
digiworld update-scenariosto regenerate the scenario list. - Add unit tests under
tests/unit/scenarios/apps/<app_name>_app/that test the verification logic in isolation using in-memory SQLite databases. - Use the Scenario Explorer Demo to manually run the scenario on an emulator and confirm that verification works end-to-end.
Refer to existing scenarios in the same app for conventions and patterns.
Configuration is loaded at three levels, each overriding the previous:
app_config.json App-level: bundle ID, compatible profiles
scenario_config.json Scenario-level: task name, class, context fields, mockdata
instance_config.json Instance-level: parameters, profiles, difficulty metadata
Mockdata JSON files use {{placeholder}} syntax for dynamic data generation:
| Template | Resolves To |
|---|---|
{{current_user_email}} |
Logged-in user's email |
{{current_user_id}} |
Logged-in user's ID |
{{auto_id}} |
Unique auto-generated ID |
{{recent_timestamp}} |
1-48 hours ago |
{{past_timestamp}} |
Random past date |
{{random_phone}} |
Generated phone number |
{{beginning_timestamp}} |
Positioned after existing data (email/payment apps) |
{{middle_timestamp}} |
Positioned between existing data |
{{end_timestamp}} |
Positioned before existing data |
{{context_sender:work}} |
Work contact email (email app) |
{{context_sender:personal}} |
Personal contact email (email app) |
App-specific template resolvers (e.g., EmailTemplateResolver, PaymentTemplateResolver) extend the base set with domain-specific placeholders.
digiworld/
apps/ React Native applications (see apps/README.md)
digiworld/ Scenario framework, ADB layer, and data pipeline
digiworld/ Python package
__init__.py Version, path helpers, app registry re-exports
cli.py CLI entry point
app_registry.py App name/bundle ID mappings
adb/ ADB interaction layer
pipeline/ Data generation pipeline
scenarios/
scenario_base.py Abstract Scenario base class
scenario_registry.py Auto-discovery and registration
verification.py TargetStateScenario and ComposableScenario mixins
config_loader.py Configuration loading and validation
state_manager.py Database and state operations
template_resolver.py Template placeholder resolution
mockdata_handler.py Mockdata loading and insertion
mockdata_validator.py Mockdata schema validation
context_extractor.py User context extraction
positioning_service.py Timestamp positioning
answer_matchers.py Flexible answer comparison (numeric, date, duration, etc.)
constraints.py Feasibility constraint definitions
scenarios/ Per-app scenario implementations
auction/
banking/
eats/
ecommerce/
email/
flightbooking/
message/
music/
parking/
payment/
qwikshop/
ryde/
smarthome/
transit/
video/
state_data/ App state snapshots by profile
demos/ Demo applications
utils/ Utility scripts
pyproject.toml Package configuration
requirements.txt Installation dependencies
# Run all scenario tests
python -m pytest tests/unit/scenarios/ -v
# Run tests for a specific app
python -m pytest tests/unit/scenarios/apps/email_app/ -vModuleNotFoundError: No module named 'digiworld'
- Run
pip install -r requirements.txtfrom thedigiworld/directory - Verify you are in the correct virtual environment
ModuleNotFoundError: No module named 'adb_actions'
- The ADB layer is now part of the digiworld package at
digiworld.adb.actions - Run
pip install -r requirements.txtfrom thedigiworld/directory
No scenarios found in registry
- Ensure
digiworld/digiworld/scenarios/scenarios/contains scenario folders withscenario_config.json - Check that the scenario modules can be imported without errors
ADB device not found
- Ensure the Android emulator is running:
adb devices - Restart ADB:
adb kill-server && adb start-server
Missing state data or media files
- Run
git lfs pullfrom the repository root to download all LFS-tracked files
- Mock Applications -- React Native apps: building, running, per-app docs and schemas
- Evaluation Harness -- Running an agent against the apps and scoring the result
- Container Setup -- Docker / Podman image bundling the emulator, apps and eval server
- Scenario Explorer Demo -- Interactive web UI for browsing and validating scenarios
- ADB Interaction Layer -- ADB interaction library
This project is licensed under the Apache License, Version 2.0. See the LICENSE file for the full license text.