Build your application#
Overview#
Build your own XR AI application from a sample and keep its code and
configuration under apps/. This gives you an application workspace where you
can develop your task-specific behavior while using the checkout’s SDK and
services.
Start from a source checkout#
The directions below assume the application is under apps/<your-app>/. The
XR AI SDK distributions are not published to a package registry; they resolve
through relative [tool.uv.sources] entries in each application’s
pyproject.toml.
Use the latest stable release by default, falling back to the latest
prerelease when no stable release exists. Use main when the application
needs unreleased changes. The documentation and checkout must use the same
ref: use https://nvidia.github.io/xr-ai/<ref>/, replacing <ref> with
the selected release tag or main. The /latest/ alias can fall back to
main when a release lacks current entry points, so it does not guarantee
a match with a release checkout.
Create the checkout:
git clone https://github.com/NVIDIA/xr-ai.git
cd xr-ai
To use the latest stable release, or the latest prerelease when no stable release exists, run:
release_tag="$(git tag --list 'v*' | python3 .github/scripts/select_latest_docs_release.py)"
if [ -n "$release_tag" ]; then
git checkout "$release_tag"
else
echo "No release tag found; continuing on main." >&2
fi
After checking out a release, confirm that
docs/source/guides/building-your-app.md and
skills/build-your-app/SKILL.md exist in the checkout. If either is absent,
ask before switching the documentation, skill, and checkout to main.
Read AGENTS.md, then review the
system requirements and
credential sources before resolving the
application. Choose local model services or hosted endpoints. If setup already
selected one, keep that choice. The copied reference application uses local
model services by default. For hosted inference, refer to
Deploying samples with NIM endpoints; the hub still needs NVENC and NVDEC on its
host as described in the system requirements.
Choose the layers your application needs#
Package |
Use it for |
|---|---|
xr-ai-launcher |
Process management for starting DeviceIOHub, workers, and application
services in dependency order, with only |
xr-ai-hub-client |
IPC with DeviceIOHub, participant events, data and audio messages, and live frame access. |
xr-ai-logging |
Shared logging setup used by the copied orchestrator and worker. |
xr-ai-models |
Typed LLM, VLM, STT, TTS, and embedding clients selected from deployment profiles. |
xr-ai-agent-runtime |
Typed agent lifecycle, registration, publication, and participant-scoped subscriptions. |
xr-ai-tools |
Typed |
xr-ai-voice |
The |
xr-ai-voicegate |
Wake-phrase and follow-up-turn gating used by the copied voice application. |
xr-ai-web-events |
A bounded browser view for compact application events explicitly selected by the application. |
The distribution names, import names, and source directories are not always
identical. In particular, xr-ai-hub-client comes from
agent-sdk/xr-ai-hub, and xr-ai-agent-runtime comes from
agent-sdk/xr-ai-runtime. Refer to Agent SDK for package
ownership boundaries and composition guidance.
Copy the Conversation application#
agent-samples/conversation-example/ combines conversation, visual questions,
one-shot alerts, and note-taking. Copy its tracked files from the repository
root and rename its Python package and worker configuration. Keep only the
destinations your application needs. The deprecated
Simple VLM sample remains available for
the browser demo and existing integrations.
mkdir -p apps/my-app
git archive HEAD agent-samples/conversation-example |
tar -x --strip-components=2 -C apps/my-app
mv apps/my-app/worker/conversation_example_worker apps/my-app/worker/my_app_worker
mv apps/my-app/yaml/conversation_example_worker.yaml apps/my-app/yaml/my_app_worker.yaml
Replace the sample names consistently:
Existing name |
Application name |
|---|---|
|
|
|
|
|
|
Substitute the application’s real kebab-case name for my-app, then derive its
snake-case package and command names mechanically. Apply the
replacements in the listed order across the entire copied tree, including
comments and docstrings. Update any remaining agent-samples/
working-directory references to apps/, and change prose that still describes
the application as a repository sample. The following check should produce no
output:
rg -ni 'conversation[-_ ]example' apps/my-app
The apps/ and agent-samples/ directories are at the same depth, so the
copied projects’ relative sources continue to work. Keep each
[tool.uv.sources] entry paired with its distribution in
[project].dependencies. Every unpublished XR AI dependency must retain a
repository source mapping.
Use this map to separate reusable scaffolding from the sample’s capabilities:
Files |
Reuse |
Replace or adapt |
|---|---|---|
|
Keep the |
Rename the project, command, logging namespace, worker command, and worker YAML path. Update |
|
Keep the typed DeviceIOHub configuration shape. |
Set the room, ports, web client, and network behavior. Refer to the DeviceIOHub reference YAML for coupled port and URL changes. |
|
Keep the named-package layout, console entry point, argument parsing, and delegation to |
Rename the distribution, package, and entry point. Remove dependencies only after their imports and features are gone. |
|
Keep typed loading and paths resolved relative to the YAML file. |
Replace the prompt, frame, model, voice, and application-specific settings with the fields the application consumes. |
|
Keep the |
Adapt registered destinations, model roles, health probes, output topics, and context subscriptions together. Keep the router decision service while using routing. |
|
Keep participant-scoped ownership and cancellation patterns. Refer to Conversation example for their boundaries. |
Adapt or remove the current-view, alert, and note-taking destinations and their prompts, tools, and subscriptions. Do not leave removed destinations registered. |
|
Keep the adapter and endpoint separation. |
Declare only the logical model roles, client adapters, and shared endpoints the application needs. Do not add model-server deployment or readiness settings. |
|
Keep it when the application uses voice. |
Choose conversation controls or direct speech using Conversation controls, and test the application’s first spoken request. |
|
Keep a short configure and run path. |
Describe the application, its owned and reused processes, settings, and exact start commands. |
Rewrite the copied README as application-owned documentation. Replace its title
and the “Conversation example” and “This sample” descriptions, and remove links
to the Conversation sample reference. In particular, make sure the name
replacements did not leave a nonexistent /reference/my-app.html link. Change
its working directory to apps/my-app/ and use the model-server command shown
below.
The copied model-servers/ directory is an application-owned preset if you
choose to maintain it; the commands below reuse the repository’s Conversation
stack without changing its configuration.
The copied DeviceIOHub YAML leaves credentials blank, so the hub generates a
fresh pair at startup. To use a fixed pair, set LIVEKIT_API_KEY and
LIVEKIT_API_SECRET in the launch environment. Never put actual keys or tokens
in source files. Refer to Credentials for the supported
credential sources.
Frame selection and vision inference use separate public paths: import
live-frame selection from xr_ai_tools.current_frame and image inference from
xr_ai_tools.vision.
Resolve and run the application#
From apps/my-app/, resolve both projects before starting the stack:
uv --config-file ../../uv.toml sync
uv --config-file ../../uv.toml sync --project worker
Local model services#
The model launcher requires HF_TOKEN. Configure it through one of the
sources in Credentials before starting the application.
The copied application reuses speaker ASR, LLM, VLM, TTS, and the Clef decision
service. Start those shared services first and wait for the launcher to report
that all processes are ready:
uv --config-file ../../uv.toml run \
--project ../../agent-samples/conversation-example/model-servers conversation_example_model_servers
Hosted endpoints#
Follow Deploying samples with NIM endpoints to configure and validate the STT, LLM,
VLM, and TTS roles in the application’s yaml/models.json. Retain the router
role and start Clef separately or use a compatible decision endpoint as that
guide describes. Keep a copy of the local profile before replacing its entries.
Apply the guide’s voice-gate settings for hosted speech to disable the copied
local speaker backend. Start the application after endpoint validation.
Start the application#
After the selected model environment is ready, start the application from
apps/my-app/:
uv --config-file ../../uv.toml run my_app
Open the full web-client URL printed by DeviceIOHub, including its scheme and
port. For a client on another device, replace localhost with the XR AI host’s
IP address or hostname reachable by that device. HTTPS is enabled by default.
Refer to TLS for clients for certificate trust on the client
device and Browser reports an empty response on the hub port if the browser reports an empty
response.
For the copied voice-and-vision application, select Connect, grant camera
and microphone access, choose On-demand images or Live video under
Camera Mode, then select Start Microphone. Use the conversation controls
configured in yaml/voice_gate.yaml before speaking the application’s request.
The web client’s Send control delivers a typed query to the agent and
bypasses speech gating. It checks the query path without exercising STT or
conversation controls.
Write the application’s README with its intended client, exact connection steps, voice mode, and an example request. Check the adapted commands and link targets before handing the application to its user.
The application launcher owns only DeviceIOHub, the worker, and any other
application processes. The separate model-server launcher owns shared model
startup and readiness. The voice runtime checks required speaker ASR
readiness, and the copied worker checks LLM, router, and VLM health before
announcing readiness; it does not perform VLM inference as a startup warmup.
Adapt those probes to the capabilities the application retains. When a model
role changes, update yaml/models.json and the worker’s service construction.
Do not add shared model services to the application’s PROCESSES list. If a
process fails, use its launch context and run-log directory to investigate through
Troubleshooting.
Verify without hardware#
Turn each requirement in the application brief into an observable acceptance
case. Include configuration defaults when a key is omitted, the intended first
request, and the recovery behavior for unavailable inputs. Keep these checks in
apps/my-app/tests/ using the layout and command below.
From the repository root, load the worker configuration before starting services. This check imports the renamed package, reads the application YAML, and resolves packaged prompt files:
uv --config-file uv.toml run --project apps/my-app/worker \
python - <<'PY'
from pathlib import Path
from my_app_worker.config import load_config
load_config(Path("apps/my-app/yaml/my_app_worker.yaml"))
PY
Exercise model wire behavior with tests/_stub_openai.py, which provides an
httpx.MockTransport implementation of OpenAI-compatible endpoints. The wire
tests exercise xr_ai_models clients without importing the application worker.
The deprecated sample’s tests/test_simple_vlm_example_wire.py remains a small
STT, VLM, and TTS wire-format test pattern. Use
tests/test_xr_render_demo_wire.py for LLM tool-call flows. For the copied
application’s routing, context, and destination behavior, adapt the CPU cases
in tests/test_conversation_example_worker.py and
tests/test_conversation_example_agents.py; use a fake decision service
rather than starting Clef for these checks.
The hub, make_connector, and make_processor fixtures in
tests/conftest.py run DeviceIOHub IPC over local ZMQ sockets. They demonstrate
participant routing and worker-facing endpoint coverage without LiveKit, a
camera, a microphone, Docker, or a GPU. Pytest does not load that file for
tests under apps/my-app/ because it is not an ancestor of the application
test directory. Keep application-owned fixtures in
apps/my-app/tests/conftest.py; copy and adapt only the repository fixtures the
application needs.
Add the application’s pytest settings to apps/my-app/pyproject.toml:
[tool.pytest.ini_options]
asyncio_mode = "auto"
pythonpath = ["worker"]
markers = [
"gpu: requires local GPU, Docker, or NVENC",
"integration: starts a real service process",
]
Point model clients at a StubOpenAI-style transport, then run the application
test directory explicitly with the repository test environment:
uv --config-file uv.toml run --project tests \
pytest -v apps/my-app/tests -m "not gpu"
Refer to Testing for the complete test commands and marker rules.
Before reporting that the application is complete, match each requirement to its test or client-interaction result. Exercise the intended first request through a real client after the hardware-free checks pass. Record the selected model environment, configuration, observed response, and any simulated media or unverified requirement. Distinguish typed input from spoken input and record whether STT and the configured conversation controls were exercised. Passing tests and service readiness do not replace that client check.
Understand the application boundary#
Repository file checks exclude the top-level apps/ directory. Application
projects do not enter DEPENDENCIES.md or the dependency manifest, and the
repository’s Ruff and SPDX checks do not inspect application-owned files. The
sample documentation, configuration catalogs, and test discovery remain scoped
to agent-samples/ and tests/. Git ignores apps/* by default so private
application work is not staged accidentally. Remove that ignore rule when the
application owner wants to track the application in the fork.
The rest of the checkout remains repository-owned. Commit-wide hooks such as
DCO sign-off still run when installed, and changes outside apps/ continue to
follow AGENTS.md and the repository’s contribution checks.