# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
This site has an agentic ask interface you may use to query the documentation dynamically by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.healthuniverse.com/overview/index.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

---

# Overview of Health Universe

### Welcome!

We are thrilled to have you join our community of developers, researchers, and healthcare professionals working together to revolutionize the healthcare industry. Before you dive into the platform, let's start by introducing you to Health Universe and its objectives.

### What is Health Universe?

Health Universe is an innovative platform dedicated to transforming healthcare through data-driven applications. By offering a centralized app cloud that gathers, organizes, and facilitates the deployment of Python applications, Health Universe empowers medical professionals, researchers, and developers to harness the power of cutting-edge technologies, ranging from basic scientific models to advanced machine learning and AI.

Our mission is to bridge the gap between healthcare and technology, fostering collaboration and innovation in the pursuit of improved patient outcomes, more efficient healthcare systems, and accelerated discovery of new treatments and cures. It's time to bring the delivery of health research into the 21st century.

### Key Features

1. **Launch Apps with 1-Click**: The platform is designed to be accessible to users of all technical levels, embracing Streamlit to provide intuitive UI, FastAPI, and Google A2A. Our cloud to provide easy deployment and management of data science health applications. No downloads or setup required.
2. **Collaborative Ecosystem**: Health Universe fosters a collaborative environment where developers, researchers, and healthcare professionals from anywhere in the world can work together to solve complex healthcare challenges and create innovative solutions.
3. **Cutting Edge ML/AI Support**: By integrating the latest machine learning and AI libraries, Health Universe keeps its users at the forefront of healthcare innovation, enabling them to build and deploy cutting-edge applications that drive real-world impact.
4. **Scalable and Secure**: Health Universe is built to scale, allowing for the growth and expansion of healthcare applications while maintaining high levels of security and compliance with data privacy regulations. Our engineers have worked on the largest high availability e-commerce platforms in existence, and are now paving the way forward with Health Universe.

We are excited to have you as part of the Health Universe community and look forward to the incredible collaborations that will arise from our collective efforts. Together, we can shape the future of healthcare and make a lasting impact on the lives of millions around the globe.

In the very near term, we see a future where health researchers are able to commercialize their applications and are rewarded for their intellectual property contributions in our marketplace. Don't miss out on this exciting future. Get started by deploying your app to Health Universe today!


# How Health Universe Works

Health Universe provides a simple, secure, and streamlined way to build, deploy, and share healthcare applications.

<figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2Fgit-blob-f8aa2f3555bc3ff5d898621ff21704a79e378a73%2Fhu-architecture.png?alt=media" alt="Health Universe platform architecture"><figcaption><p>Health Universe connects directly to your core systems and provides a unified platform for building and orchestrating AI agents.</p></figcaption></figure>

Here’s a high-level overview of how it all fits together:

### 1. App Development

* [**Workspaces**](/overview/building-apps-in-health-universe/creating-a-workspace)**:**\
  Workspaces are collaborative environments where teams or individuals can develop and manage apps. In private workspaces, you can work solo and choose to make your apps public, while shared workspaces allow team members to collaborate and deploy apps together.
* **Repositories:**\
  Each app is linked to a GitHub repository. Your source code (e.g., Streamlit or FastAPI app) lives there.
* **Frameworks:**\
  Apps can be built using [**Streamlit**](/overview/building-apps-in-health-universe/developing-your-health-universe-app/working-in-streamlit/streamlit-best-practices) (for fast UIs) or [**FastAPI**](/overview/building-apps-in-health-universe/developing-your-health-universe-app/working-in-fastapi/navigator-fastapi-best-practices) (for APIs and backend services).

### 2. Deployment Pipeline

* [**Connecting GitHub**](/overview/building-apps-in-health-universe/getting-started-with-health-universe/link-your-github-account-to-your-health-universe-account)**:**\
  You connect your GitHub account to Health Universe.\
  Health Universe automatically syncs your repositories.
* [**Deployment**](/overview/building-apps-in-health-universe/deploying-your-app-to-health-universe/deploying-to-health-universe)**:**\
  When you're ready, you manually trigger a deployment from your workspace. Health Universe pulls your code, installs necessary packages, and builds your app environment automatically.
* [**Secret Management**](/overview/building-apps-in-health-universe/deploying-your-app-to-health-universe/secret-management)**:**\
  Sensitive credentials (e.g., API keys) are handled through integrated secret management — not stored in your code.

### 3. App Hosting

* **Visibility Settings:**\
  You can choose whether your app is:
  * **Private** (just for you or your team)
  * **Public** (shareable with the Health Universe community)

### 4. Integrations & Extensions

* [**LLM (Large Language Model) Connections**](/overview/building-apps-in-health-universe/deploying-your-app-to-health-universe/connecting-to-an-llm)**:**\
  Health Universe makes it easy to integrate GPT models and other LLMs into your healthcare apps.
* [**External Data Sources**](/overview/building-apps-in-health-universe/deploying-your-app-to-health-universe/connecting-to-an-external-data-source)**:**\
  Apps can securely connect to FHIR servers, public health APIs, custom databases, and more.
* [**External Libraries**](/overview/additional-resources/external-tools-and-libraries)**:**\
  You can include external Python libraries to extend your app’s functionality, subject to platform security policies.

### 5. Testing & Redeploying

* [**Testing**](/overview/testing-your-app)**:**\
  Test your app directly within your workspace before public launch.
* [**Re-deployments**](/overview/re-deploying-your-app)**:**

  Changes pushed to GitHub do not automatically redeploy your app.\
  After updating your code, you must manually trigger a redeployment in Health Universe to apply the latest changes.

## Summary

**Health Universe is purpose-built for healthcare developers, combining modern app development tools with healthcare-specific standards:**\
🔹 Secure and compliant app hosting\
🔹 Seamless GitHub integration\
🔹 Support for healthcare data standards\
🔹 Rapid development with Streamlit or FastAPI\
🔹 Easy extensions with external data or AI models


# 15 Second Quick-Start

This quick-start will guide you through packaging your python app with Streamlit or FastAPI and deploying it to Health Universe, the open-source health research cloud for ML/AI.

## Prerequisites

* An idea for a health application you'd like to create
* Familiarity with Python and either Streamlit or FastAPI
* A GitHub account and a Health Universe account


# Streamlit Quick-Start

This quick-start will guide you through packaging your python app with Streamlit and deploying it to Health Universe. You will need to have a GitHub account to create your repository.

## Step 1: Create a GitHub repository

1. Create a new repository on GitHub to host your project. You can make the repository either public or private, and it is a good idea to have a readme.md file in the repository and use a .gitignore template so that you can ignore the .env. This is important when you're linking a local development environment to a github repository.

   * [x] **Repo contains main.py and requirements.txt**
   * [x] **Repo has a readme.md**
   * [x] **Use the "python" .gitignore template (to ignore .env)**

   Note: If you're using a local development environment, link your local environment to your GitHub repository<br>
2. Clone the repository to your local machine:

   `git clone https://github.com/<your_account>/<your_repo_name>.git`<br>
3. Change to your project directory:

   `cd <your_repo>`<br>
4. Create a virtual environment using:\
   `python -m venv .env`<br>
5. Activate your virtual environment with:

   `source .env/bin/activate`<br>
6. Upgrade pip to make sure things go smoothly:\
   `python -m pip install --upgrade pip`<br>
7. Install the streamlit library with:\
   `pip install streamlit`

#### If you're using ChatGPT (or editing directly into GitHub)

You will need to have at minimum a main.py file, and a requirements.txt file. you can copy/paste code into these files directly in GitHub.

1. Create a main.py file in your repository. This is where your code should reside.

```python
import streamlit as st

st.title("Welcome to Health Universe!")
st.write("This is a sample application.")

alarm_clock = st.slider('hour', 0, 23, 17) # min: 0h, max: 23h, default: 17h
st.header(alarm_clock) # print in large text
```

1. Create a requirements.txt file. For streamlit, it should include (at a minimum) Streamlit as a requirement.

```python
streamlit
# Include any other libraries required by your app.
```

Make sure that you commit any changes that you make before you deploy with health universe.

## Step 2: Create your python app within your local environment

In your local repository, create the following files:

* **main.py**: This file should contain your Python model.
* **requirements.txt**: This file will list your project dependencies.

Next, learn a little bit about how Streamlit works.

* [Overview of the main concepts](https://docs.streamlit.io/library/get-started/main-concepts) for creating UI with Streamlit.
* [Here's a nifty cheat sheet](https://docs.streamlit.io/library/cheatsheet) for all the interface elements you can create.

### main.py

```python
import streamlit as st

st.title("Welcome to Health Universe!")
st.write("This is a sample application.")

alarm_clock = st.slider('hour', 0, 23, 17) # min: 0h, max: 23h, default: 17h
st.header(alarm_clock) # print in large text
```

### requirements.txt

```txt
streamlit
# Include any other libraries required by your app.
```

**Test out your app by running it locally:**

Run your app locally using: `streamlit run main.py`

## Step 3: Deploy to Health Universe

1. Push your local repo to GitHub.
2. Log in or create a Health Universe account if you haven't already.
3. Go to [https://healthuniverse.com](https://www.healthuniverse.com) and navigate to "Apps."
4. Click "Add App" to create a new app.
5. Fill out the following fields:
   * App Name: Name of your app
   * Description: A brief description of your app
   * Github Account: Your GitHub username
   * Github Repo: The exact name of your GitHub repository
   * Main File: The name of your python file (usually `main.py`)
6. Click "Add App" to deploy your app. This process may take a few minutes.

Once you've completed these steps, your app will be published on Health Universe!

## 🥳🥳🥳


# FastAPI Quick-Start

This quick-start will guide you through packaging your python app with FastAPI and deploying it to Health Universe, the open-source health research cloud for ML/AI.

## Step 1: Create a GitHub repository

1. Create a new repository on GitHub to host your project. You can make the repository either public or private, and it is a good idea to have a readme.md file in the repository and use a .gitignore template so that you can ignore the .env. This is important when you're linking a local development environment to a github repository.

   * [x] **Repo contains main.py and requirements.txt**
   * [x] **Repo has a readme.md**
   * [x] **Use the "python" .gitignore template (to ignore .env)**

   Note: If you're using a local development environment, link your local environment to your GitHub repository<br>
2. Clone the repository to your local machine or use our [FastAPI Template](https://github.com/Health-Universe/fastapi-template-demo):

   `git clone https://github.com/<your_account>/<your_repo_name>.git`<br>
3. Change to your project directory:

   `cd <your_repo>`<br>
4. Create a virtual environment using:\
   `python -m venv .env`<br>
5. Activate your virtual environment with:

   `source .env/bin/activate`<br>
6. Upgrade pip to make sure things go smoothly:\
   `python -m pip install --upgrade pip`<br>
7. Install the FastAPI, Uvicorn, & Pydantic libraries with:

   `pip install fastapi uvicorn pydantic`

## Step 2: Create your python app

In your local repository, create the following files:

* **main.py**: This file should contain your Python model.
* **requirements.txt**: This file will list your project dependencies.

Next, learn a little bit about how FastAPI works.

* [Overview of the main concepts](https://fastapi.tiangolo.com/tutorial/) for creating UI with FastAPI.
* [Here's a nifty cheat sheet](https://fastapi.tiangolo.com/tutorial/query-params/) for all the parameters you can create.

### main.py

For FastAPI apps, it is best to work from a [template](https://github.com/Health-Universe/fastapi-template-demo) so that Navigator knowns how to interact with your app. The template is listed below:

```python
from typing import Annotated, Any
import httpx
from fastapi import FastAPI, Form
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field


# Input and Output Schemas
class Input(BaseModel):
    """
    Form-based input schema for template.
    """
    data: Any = Field(
        ...,
        title="An applicable title is helpful for the navigator.",
        examples=["Examples are helpful for navigator to understand the data."],
        description="Always provide verbose description for the data.",
    )


class Output(BaseModel):
    """
    Form-based output schema for template.
    """
    data: Any = Field(
        ...,
        title="An applicable title is helpful for the navigator.",
        examples=["Examples are helpful for navigator to understand the data."],
        description="Always provide verbose description for the data.",
    )


# FastAPI app initialization
app = FastAPI(
    title="Navigator Tool Template",
    description=(
        "The Navigator tool template provides a starting point for creating new Navigator tools. "
    ),
    version="1.0.0",
)

# CORS middleware configuration
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Tool endpoint
@app.post("/call/", response_model=Output)
async def call(data: Annotated[Input, Form()]) -> Output | JSONResponse:
    """
    Process the input data and return the output.

    Args:
        data: The input data.

    Returns:
        The output data.
    """
    async with httpx.AsyncClient() as client:
        try:
            return Output(**data.model_dump())
        except (httpx.RequestError, httpx.HTTPStatusError) as e:
            return JSONResponse(
                {"error": f"Request failed: {e}"}, status_code=500
            )


# Health check endpoint
@app.get("/health", response_class=JSONResponse)
async def health_check():
    """
    Health check endpoint.
    """
    return {"status": "Application is running."}

```

### requirements.txt

For FastAPI apps, at a minimum you should include the requirements listed below. You may have additional requirements specific to your app, so be sure to include those as well.

```txt
fastapi
python-multipart
uvicorn
httpx
pydantic
# Include any other libraries required by your app.
```

**Test out your app by running it locally:**

1. Run your app locally using:

   `uvicorn main:app --reload`
2. Use the auto-generated Swagger UI at: <http://127.0.0.1:8000/docs>

## Step 3: Deploy to Health Universe

1. Push your local repo to GitHub.
2. Log in or create a Health Universe account if you haven't already.
3. Go to [https://healthuniverse.com](https://www.healthuniverse.com) and navigate to "Apps."
4. Click "Add App" to create a new app.
5. Fill out the following fields:
   * App Name: Name of your app
   * Description: A brief description of your app
   * Github Account: Your GitHub username
   * Github Repo: The exact name of your GitHub repository
   * Main File: The name of your python file (usually `main.py`)
6. Click "Add App" to deploy your app. This process may take a few minutes.

Once you've completed these steps, your app will be published on Health Universe!


# Getting started with Health Universe

Health Universe is an AI/ML deployment platform that provides a fast, compliant way to develop and deploy apps within health environments. It is based on Python, and uses Streamlit for stand alone apps (that can leverage native app orchestration tools and workflows), and FastAPI to integrate into the Health Universe Navigator workflow orchestration engine.

The first step(s) are to create a Health Universe account, create a GitHub account, and to link the two accounts together.


# Create a Health Universe Account

Before you begin, you should create a Health Universe Account.

To create an account on Health Universe:

#### 1. Visit the Sign-Up Page

Go to: <https://www.healthuniverse.com/sign-up>

#### 2. Fill in Your Information

Complete the sign-up form with the following details:

* **First Name**
* **Last Name**
* **Email Address**
* **Password**

Make sure you use a valid email address—you’ll need it to verify your account.

<figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FzbwRdfiBZYbFAAJWWNg9%2FScreenshot%202025-04-23%20at%203.17.33%E2%80%AFPM.png?alt=media&amp;token=4a350829-4e0f-4be2-adc9-c5773dc09211" alt=""><figcaption></figcaption></figure>

#### 3. Confirm Your Email

After submitting the form, you'll receive a confirmation email.\
📧 **Open your inbox** and click the confirmation link in the message from Health Universe.

<div><figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2Fgit-blob-2c6645e55eab880dea6437146a5da669d989cfec%2FIMG_6445.png?alt=media" alt="" width="295"><figcaption></figcaption></figure> <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2Fgit-blob-040907610f04c5fd78e32c50b887b5e01f56acbf%2FIMG_6446.png?alt=media" alt="" width="295"><figcaption></figcaption></figure></div>

#### 4. Access Your Workspace

Once confirmed, you'll be redirected to:\
👉 <https://www.healthuniverse.com/workspaces>

From here, you can start creating or joining apps.


# Create a Github Account

Before deploying applications with Health Universe, you’ll need a GitHub account.

* **If you don’t have a GitHub account:**\
  Visit [www.github.com](https://www.github.com/) and create one by following the sign-up instructions. Health Universe connects your account to your GitHub user profile.
* **If you already have a GitHub account:**\
  You can skip this step and proceed to [linking your GitHub account ](/overview/building-apps-in-health-universe/getting-started-with-health-universe/link-your-github-account-to-your-health-universe-account)to your Health Universe account.

<figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2Fki85gLKFj6iFnyhdx41M%2Fimage.png?alt=media&amp;token=8c5f0c77-0816-4d36-a410-c08e0649c797" alt=""><figcaption><p>Sign up to GitHub</p></figcaption></figure>


# Link your Github Account to your Health Universe Account

Once you have a Health Universe account and a Github account, you need to **link** the two accounts together. This will make deploying your apps seamless.

In the upper right corner of the website, click on the User Settings button and select `Settings ⛭`

<figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FSbxCBvDOJWJxi5uyrOIX%2Fimage.png?alt=media&amp;token=014cd5b9-ef4d-40e6-ac73-793f82cad33d" alt="" width="130"><figcaption></figcaption></figure>

On Profile Details, there is a space to connect an external account click on `+ Connect account` and select `Github`

You will be redirected to authenticate with your GitHub account. Enter your authentication information and (user name or email address) and password (or passkey). If you are [new to GitHub](/overview/building-apps-in-health-universe/getting-started-with-health-universe/create-a-github-account) and haven't created an account yet, you should do that first.

<figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FWmrViAmWUI6VyDV9yaCx%2Fimage.png?alt=media&amp;token=0f3d1acc-26e6-4051-8a28-391464e26744" alt="" width="171"><figcaption></figcaption></figure>

<figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FRwBEDv9frkg5zHeuZDY9%2Fimage.png?alt=media&amp;token=9ec19393-62e9-452f-bd55-442f943cb403" alt="" width="188"><figcaption></figcaption></figure>

Once you've `Authorized the Health Universe Application`, you will be directed back to Health Universe and the connection should be complete!


# Creating a Workspace

Workspaces are shared environments where teams and collaborators can work together on apps. Only members of your workspace can view and manage the app—**unless you choose to make it public.**

**Private (individual) workspace.** When you create a Health Universe account, you'll have a private workspace. This is ideal for individual projects. They can be made public so that others can use the apps you create, but only you will be able to develop and deploy those apps. FastAPI apps that are deployed in your individual workspace **cannot** be moved into a shared workspace — you will need to redeploy those apps into the shared workspace.

**Shared Workspaces:** Shared workspaces are used for team or enterprise projects. The person who creates the workspace can then invite other members of their team to that workspace. Shared workspaces are ideal for team projects or projects that require multiple apps to work together within the Navigator environment. Apps within a workspace will be available within the instance of Navigator within that workspace.

## To create a shared workspace:

#### 1. Go to the Workspaces Page

Navigate to:\
👉 <https://www.healthuniverse.com/workspaces>

#### 2. Create a New Organization

The New organization can be names for the project team, the organization, or the enterprise. It is best to make it unique to the scope of the project that you are working on. Only one person on the team (the team leader), should need to create the project workspace.

* In the **top-left corner**, click the dropdown next to your **personal account**.

  <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FMjKJHWvRycRk6d2CqXHC%2FScreenshot%202025-05-28%20at%209.56.18%E2%80%AFAM.png?alt=media&amp;token=8da1b14f-e6c1-4af0-b2ff-f25a4515b3a8" alt=""><figcaption></figcaption></figure>
* Select **Create Organization**.
* A pop-up window will appear.

  <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FhJLHIaRKse75xlvbttV6%2FScreenshot%202025-05-28%20at%209.57.20%E2%80%AFAM.png?alt=media&amp;token=fce3a29a-21e2-4a93-bd00-65e6063ccc6f" alt="" width="375"><figcaption></figcaption></figure>

#### 3. Fill Out Organization Info

In the pop-up:

* Upload your **organization logo** (optional)
* Enter a **name** for the workspace
* The **slug** will autofill based on your name (e.g. `my-org`)
* Click **Create Organization**

#### 4. Invite Members (Optional)

After creating the organization, a second pop-up will appear to invite team members:

* Add email addresses (separated by commas)
* Select each person's **role** from the dropdown:
  * **Member** – can view and use the app
  * **Admin** – can manage settings, deployments, and members
* Click **Send Invitations** or choose **Skip** to add members later

✅ You’ll see a confirmation message: **"Invitations successfully sent"**\
Click **Finish** to complete setup.

<div><figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2F42uWsjPyDbG0AIDvgelN%2FScreenshot%202025-04-23%20at%203.49.21%E2%80%AFPM.png?alt=media&amp;token=a252a13a-9670-4f64-b2fc-72d4f624d04a" alt=""><figcaption></figcaption></figure> <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2Ftn6LjMjxylYlDIbrSX4L%2FScreenshot%202025-04-23%20at%203.49.39%E2%80%AFPM.png?alt=media&amp;token=949e3263-f5cf-4af2-88ce-c5528b93557b" alt=""><figcaption></figcaption></figure></div>

#### 5. Confirm Members

Invited users will receive an email and can accept the invitation to join your organization.

<figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FbRqEtezEjbzNKwAU5g0N%2FScreenshot%202025-04-23%20at%203.36.43%E2%80%AFPM.png?alt=media&amp;token=c9ad225e-8503-4002-91da-735bff19c925" alt="" width="563"><figcaption></figcaption></figure>

To check their status:

* Go to your workspace
* In the **left-hand menu**, click **Settings > Members**
* You’ll see a list of all current members and invited members

#### 🔄 Inviting More Members Later

Admins can invite more people anytime:

1. Go to your workspace, then in the left-hand menu select **Settings>Members**
2. Click **Invite** (top-right corner)
3. Add emails, select roles, and send!

#### ⚙️ **Managing Workspace Settings**

Within your workspace **Settings**, you can access the following options:

* **General**:
  * Update your **Organization Profile** (name, slug, logo)
  * **Leave Organization**
  * **Delete Organization**
* **Members**:
  * View current members
  * Invite or remove members
  * Adjust roles and permissions


# Collaborators

Collaborators in Health Universe allow you to work together with others on your app within a **Workspace**.

Collaborators can:

* **Redeploy** the application
* **Delete** deployments
* **Modify** app metadata:
  * Name
  * Description
  * Evidence
  * Owner’s Insights
  * And more

> ⚠️ Only users with access to the app in a Workspace can be added as collaborators.

### How to Add Collaborators

Follow these steps to add collaborators to your application:

1. **Navigate to Your App's Page**\
   From your Health Universe workspace, select the app you want to collaborate on.
2. **Open the Collaborators Tab**\
   Click **Collaborators**, on the top navigation bar.

   <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FnkdSF73v74O33GHf4juJ%2FScreenshot%202025-05-28%20at%2010.27.28%E2%80%AFAM.png?alt=media&amp;token=3290c7ed-a8d5-4565-8b9e-3a05fcb42a6e" alt=""><figcaption></figcaption></figure>
3. **Click "Add Collaborators"**\
   On the Collaborators page, click the **purple "Add Collaborator"** button.

   <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2Fwjyqfnh7HMvKVhj8Etqr%2FScreenshot%202025-05-28%20at%2010.29.31%E2%80%AFAM.png?alt=media&amp;token=a951cde5-acf9-4678-a697-91eb16590154" alt=""><figcaption></figcaption></figure>
4. **Enter Email Addresses**\
   A pop-up will appear titled **"Add collaborator"**. Enter the collaborator's email.

   <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FlNlVvKiFyqSISYXlbyAh%2FScreenshot%202025-05-28%20at%2010.30.12%E2%80%AFAM.png?alt=media&amp;token=184215fd-67ab-4133-bbe2-3aa81c85ef0d" alt="" width="563"><figcaption></figcaption></figure>
5. **Submit**\
   After entering the emails, click **Add Collaborater**.
6. **View Updated Collaborator List**\
   You’ll be redirected to the updated Collaborators page, where you’ll see the list of newly added collaborators. Repeat process for other collaborators.

   <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FCLsrEsmsoXfg9VtRe1DL%2FScreenshot%202025-05-28%20at%2010.31.57%E2%80%AFAM.png?alt=media&amp;token=88373e5c-3712-4369-879a-b1bec76334d7" alt=""><figcaption></figcaption></figure>

### Notes

* Collaborators must have a [Health Universe](https://www.healthuniverse.com/) account.
* Only app creators and collaborators can manage deployment-related tasks and metadata.
* Consider adding trusted team members with a clear understanding of your app’s purpose and data.

For more information on setting up apps, see [Creating a Workspace](https://docs.healthuniverse.com/overview/building-apps-in-health-universe/creating-a-workspace).


# Developing your Health Universe App


# Streamlit vs FastAPI vs A2A vs React

Health Universe supports four app runtimes. Each is the right tool for a different job:

* **A2A Agents** — reusable clinical agents and workflows that run inside Navigator.
* **FastAPI** — deterministic Python tools (e.g. calculators) that appear as Navigator tools.
* **React** — bring-your-own-frontend standalone apps with a full custom UI via the typed SDK. **Preferred for real products.**
* **Streamlit** — the fastest path for a simple Python tool or prototype.

***

#### **What is Streamlit?**

Streamlit is an open-source Python framework for building interactive data apps and dashboards with minimal code. It's optimized for rapid prototyping and visualization, making it ideal for healthcare professionals and data scientists who want to quickly turn scripts into shareable web apps.

***

#### **What is FastAPI?**

FastAPI is a modern, high-performance web framework for building APIs with Python. It's built on top of Starlette and Pydantic, offering robust support for data validation, async programming, and integration with backend systems. On Health Universe, FastAPI is the way to ship a deterministic Python tool — a calculator or transform — that Navigator can surface and call as a tool.

***

#### **What are A2A Agents?**

A2A (Agent-to-Agent) agents are specialized applications built using the Health Universe A2A SDK. These agents enable AI-driven workflows with features like document processing, progress tracking, inter-agent communication, and long-running background tasks. They're reusable clinical agents and workflows designed specifically for Health Universe's Navigator platform, and support advanced workflow orchestration.

***

#### **What is the React runtime?**

The React runtime lets you **bring your own frontend**. Instead of a generated UI, you build a fully custom single-page app (React/Vite) or server app (Next.js/Node) and talk to the platform through the typed **`@health-universe/react`** SDK — typed React Query hooks for routines, documents, patients, sources/bundles, chat threads, and clinical tools. It's the preferred runtime for building **real, production clinical products** where you need full control of the user experience. See [Working in React](/overview/building-apps-in-health-universe/developing-your-health-universe-app/working-in-react).

***

#### **Side-by-Side Comparison**

| Feature                   | **Streamlit**                              | **FastAPI**                                                          | **A2A Agents**                                                | **React**                                                                      |
| ------------------------- | ------------------------------------------ | -------------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| **Primary Use Case**      | Simple Python tool / prototype; dashboards | Deterministic Python tools (calculators) surfaced as Navigator tools | Reusable AI-driven clinical agents & workflows                | Bring-your-own-frontend standalone apps (full custom UI)                       |
| **Language / Stack**      | Python                                     | Python                                                               | Python (A2A SDK)                                              | TypeScript / React / Next.js / Node                                            |
| **UI/UX Focus**           | Built-in widgets and layout for rapid UI   | No native UI; surfaces as a Navigator tool                           | Navigator integration with automatic progress tracking        | Fully custom UI — your own design system and routing                           |
| **Data Access**           | Manual file upload and processing          | Inputs passed in by Navigator/caller                                 | Built-in document client with NestJS/S3 integration           | Typed `@health-universe/react` SDK (patients, documents, routines, bundles, …) |
| **Navigator Integration** | Standalone apps                            | Appears as a tool Navigator can call                                 | Native AI workflows + inter-agent communication               | Standalone; can read Navigator threads via the SDK                             |
| **Background Tasks**      | Limited; relies on session state           | Request-response; should return quickly                              | Automatic background job management with progress persistence | Client-side React Query; long work runs server-side via routines/agents        |
| **Deployment Style**      | Static-ish standalone app                  | API-first; runs as a Python service                                  | Workflow-native; designed for Navigator orchestration         | Static SPA (React) **or** Node server (Next.js/Node), auto-detected            |
| **Best For**              | Prototypes, quick data tools, solo apps    | Calculators and deterministic tools for Navigator                    | AI workflows, document analysis, complex healthcare processes | Production clinical products with bespoke UX                                   |

***

#### **When to use each runtime**

**✅ Use A2A Agents when:**

* You are building a **reusable clinical agent or workflow** that runs inside Navigator.
* You need **long-running background tasks** (analyzing large datasets, generating reports, etc.).
* Your workflow involves **multiple agents communicating** to complete a complex process.
* You want **automatic progress tracking**, document persistence, and artifact management in Health Universe threads.

**✅ Use FastAPI when:**

* You are building a **deterministic Python tool** — a calculator, transform, or scorer — that should appear as a **Navigator tool**.
* You want fine-grained control over request/response handling with robust validation (Pydantic).
* Your tool can return a result quickly (Navigator tools should respond within \~15 seconds).

**✅ Use React when:**

* You are building a **standalone product with a full custom UI**, not a single tool.
* You want the **complete clinical data surface** (patients, documents, routines, sources/bundles, threads) through typed hooks.
* You want client-side routing, your own design system, and purpose-built clinical workflows.
* This is the **preferred path for real products**. See [Working in React](/overview/building-apps-in-health-universe/developing-your-health-universe-app/working-in-react).

**✅ Use Streamlit when:**

* You want the **fastest path** to a simple Python tool or prototype.
* You need to **rapidly iterate** on a data-driven UI with minimal code.
* You are focused on **visualizing health data or models** rather than building a polished product.

***

#### **Choosing between them**

A quick rule of thumb:

* Need a **product** with a custom UI? → **React**.
* Need a **deterministic tool inside Navigator**? → **FastAPI**.
* Need a **reusable AI agent/workflow**? → **A2A**.
* Need something **quick and Python-only**? → **Streamlit**.

FastAPI and Streamlit are the quickest ways to get a single capability live. A2A is the home for AI-driven, reusable workflows. React is where you build the full clinical product experience on top of the platform's data through the typed SDK.


# Working in Streamlit

Streamlit is an open-source Python framework for building interactive web apps with just a few lines of code. It’s designed for simplicity, making it ideal for healthcare professionals and data scientists who want to visualize models, share tools, or build decision support apps—without needing frontend expertise.

Key features:

* **Fast prototyping** with Python scripts
* **Built-in widgets** for user input
* **Auto-refreshing UI** for live interactivity
* **No HTML/CSS/JS required**

🔗 **Official Resources**

* Streamlit Documentation: <https://docs.streamlit.io>
* Streamlit GitHub: <https://github.com/streamlit/streamlit>

In the **Health Universe Community**, Streamlit is perfect for creating and sharing **clinical dashboards**, **data exploration tools**, and **stand-alone clinical calculators**—ideal for solo apps or small teams focused on rapid innovation and usability.

For Health Universe apps

Make


# Typical Project Setup

Now that you have installed the required software, it's time to create a basic Streamlit project. In this section, we'll guide you through creating a new project, configuring your development tools, and setting up a basic application structure.

### Step 1: Create a GitHub Repository

Go to GitHub and create a new public repository. At this time, the repo must be public in order for Health Universe to be able to deploy your project.

### Step 2: Clone Your Repository

Run this in your terminal:

`git clone https://github.com/<your_account>/<your_repo_name>.git`

### Step 3: Set Up a Virtual Environment

Setting up a virtual environment for your Health Universe projects is required as it creates the requirements.txt file needed to specify your application's dependencies. To create a virtual environment, follow these steps:

1. Open a terminal and navigate to your desired project directory.
2. Create a new virtual environment with the following command:

   ```css
   python -m venv .env
   ```
3. Activate the virtual environment:
   * **Windows**:

     ```
     .env\Scripts\activate
     ```
   * **macOS and Linux**:

     ```bash
     source .env/bin/activate
     ```

Once activated, your terminal should display the environment name, e.g., `(venv)`.

### Step 4: Install Streamlit

Streamlit is an open-source app framework that allows you to create and share custom web applications for machine learning and data science. Health Universe uses Streamlit to build and deploy interactive healthcare applications. To install Streamlit, run the following command in your terminal:

```
pip install streamlit
```

Wait for the installation to complete. Streamlit is now installed on your system and ready for use in your Health Universe projects.

We recommend [this page to get up to speed quickly with Streamlit](https://docs.streamlit.io/library/get-started/main-concepts).

### Step 5: Edit App.py

As a starting point, let's create a simple "Hello, World!" application using Streamlit. Open `app.py` in your text editor or IDE and add the following code:

```python
import streamlit as st

st.title("Welcome to Health Universe!")
st.write("This is a sample application.")
```

There are many different Streamlit components that make it easy to create rich UI for your health research applications. Learn more about Streamlit and [find advanced components here](https://streamlit.io/components).

Save the `app.py` file.

### Step 6: Run the Streamlit App

To run your "Hello, World!" Streamlit app, open the terminal, navigate to your project directory, and run the following command:

```arduino
streamlit run app.py
```

Streamlit will launch your app in your default web browser, displaying the "Hello, World!" message. To stop the app, press `Ctrl+C` in the terminal.

### Step 7: Run pip freeze

From your terminal, run:

```
pip freeze > requirements.txt
```

This command will save all your project's dependencies into a requirements.txt file. This file is read by the Health Universe cloud to know what libraries your app will require.

### Step 8: Commit Your Code and Push to GitHub

### Step 9: Deploy Your App on Health Universe


# Your First Health Universe App

With your development environment set up, it's time to create your first healthcare app using Health Universe and Streamlit. In this tutorial, we will guide you through building a simple app that visualizes healthcare data using interactive charts.

### Step 1: Prepare Your Data

For this tutorial, we'll use a sample dataset containing information about patients, such as age, gender, and health metrics. You can find a suitable dataset online or create a CSV file with the following columns: `Patient_ID`, `Age`, `Gender`, `Height`, `Weight`, `Blood_Pressure`, and `Heart_Rate`.

Save the CSV file as `patient_data.csv` in the `data` directory of your project.

### Step 2: Load the Data in Your App

To load the data in your app, we'll use the Pandas library. If you haven't installed Pandas, run the following command in your terminal:

```
pip install pandas
```

Next, open `app.py` in your text editor or IDE and import the Pandas library by adding the following line at the beginning of the file:

```python
import pandas as pd
```

Now, modify the `main()` function to load the patient data and display it as a table using Streamlit:

```python
def main():
    st.title("Healthcare Data Visualization App")
    
    # Load patient data
    patient_data = pd.read_csv("data/patient_data.csv")

    # Display data table
    st.write("Patient Data:")
    st.write(patient_data)
```

Save the `app.py` file.

### Step 3: Add Interactive Data Filtering

To allow users to filter the data based on age and gender, add the following code to the `main()` function, right after displaying the data table:

```python
# Filter data by age
min_age = st.sidebar.slider("Minimum Age", int(patient_data["Age"].min()), int(patient_data["Age"].max()), int(patient_data["Age"].min()))
max_age = st.sidebar.slider("Maximum Age", int(patient_data["Age"].min()), int(patient_data["Age"].max()), int(patient_data["Age"].max()))

# Filter data by gender
gender = st.sidebar.selectbox("Gender", ["All", "Male", "Female"])

# Apply filters
filtered_data = patient_data[(patient_data["Age"] >= min_age) & (patient_data["Age"] <= max_age)]
if gender != "All":
    filtered_data = filtered_data[filtered_data["Gender"] == gender]

# Display filtered data
st.write("Filtered Patient Data:")
st.write(filtered_data)
```

This code adds interactive sliders and a dropdown menu to the sidebar, allowing users to select minimum and maximum ages and a gender. The filtered data is then displayed as a table in the main content area.

### Step 4: Visualize the Data

To visualize the filtered data, we'll create a bar chart showing the average blood pressure for each age group. First, install the Plotly library by running the following command in your terminal:

```
pip install plotly
```

Next, import the Plotly library in `app.py` by adding the following line at the beginning of the file:

```python
import plotly.express as px
```

Now, add the following code to the `main()` function, after displaying the filtered data:

```python
# Aggregate data by age and calculate average blood pressure
agg_data = filtered_data.groupby("Age")["Blood_Pressure"].mean().reset_index()

# Create a bar chart of average blood pressure by age
fig = px.bar(agg_data, x="Age", y="Blood_Pressure", text="Blood_Pressure", labels={"Blood_Pressure": "Average Blood Pressure"}, title="Average Blood Pressure by Age", height=400)

# Display the bar chart in the app
st.plotly_chart(fig)
```

This code calculates the average blood pressure for each age group, creates a bar chart using Plotly Express, and displays the chart in the main content area of the app.

Save the `app.py` file.

### Step 5: Run Your Healthcare App

To run your healthcare data visualization app, open the terminal, navigate to your project directory, and run the following command:

```arduino
streamlit run app.py
```

Streamlit will launch your app in your default web browser, displaying the interactive data filters and bar chart. To stop the app, press `Ctrl+C` in the terminal.

Congratulations! You have successfully created your first healthcare app using Streamlit that you can deploy to Health Universe and share with the world. You can now build on this foundation to create more advanced applications, incorporating machine learning models, additional visualizations, and user interactivity.

<br>


# Streamlit Best Practices

Streamlit is an excellent tool for rapidly prototyping healthcare and data-driven applications. It automatically generates a user-friendly interface from your Python code, allowing you to focus on functionality and logic without needing to manually design front-end elements. This makes Streamlit a great starting point when developing apps for Health Universe.

Many developers choose to **start with Streamlit** when building a new app because:

* It allows for fast iteration: You can immediately visualize how inputs, outputs, and layout affect the user experience.
* You can debug and refine your logic more easily in a visual context.
* It supports integration with healthcare data formats like FHIR, CCDA, and CSV using native Python libraries.

Once the app’s behavior and interface are solid, developers then are able to easily migrate the codebase to FastAPI for production use with **Navigator**, where performance, modularity, and robust API control are priorities.

Here are some **best practices** to keep in mind when using Streamlit for your initial app development:

**1. Organize Your Code Clearly**

* Use functions or modular components to keep the code clean and readable.
* Separate function logic from UI logic where possible.

**2. Minimize Reruns**

* Use Streamlit’s `@st.cache_data`, `@st.cache_resource`, or `st.session_state` to store data or avoid expensive re-computations on every input change.
* Learn more: [Streamlit Caching Guide](https://docs.streamlit.io/1.27.2/library/advanced-features/caching)

**3. Use Layout Features Thoughtfully**

* Leverage columns (`st.columns()`), expanders (`st.expander()`), and sidebar components (`st.sidebar`) for better organization.
* Use `st.markdown` with custom HTML/CSS for layout tweaks when needed.

**4. Make It User-Friendly**

* Add clear labels, tooltips (`help=...`), and default values.
* Handle errors gracefully with `st.error()` or `try/except` blocks around sensitive logic.

**5. Test with Real Data**

* Test with a variety of realistic files (e.g., FHIR JSON, CCDAs) early on to uncover edge cases and formatting issues.

#### Helpful Links

* 📚 [Streamlit Documentation](https://docs.streamlit.io/)
* 🧪 [Cheat Sheet for Streamlit](https://docs.streamlit.io/library/cheatsheet)
* 💬 [Streamlit Community Forum](https://discuss.streamlit.io/)
* 🎨 [Streamlit Layout & Theming Guide](https://docs.streamlit.io/library/advanced-features/layouts)


# Working in FastAPI

FastAPI is a modern, high-performance Python web framework for building APIs quickly and efficiently. It supports automatic validation, async programming, and OpenAPI documentation out of the box—making it ideal for scalable healthcare services and backend workflows.

Key features:

* **Type hints for validation** using Pydantic
* **Asynchronous support** with `async def`
* **Auto-generated docs** via Swagger and ReDoc
* **Fast**—built on Starlette and Uvicorn

🔗 **Official Resources**

* FastAPI Documentation: <https://fastapi.tiangolo.com>
* Pydantic (data validation): <https://docs.pydantic.dev>
* Starlette (underlying toolkit): <https://www.starlette.io>

For healthcare developers, FastAPI pairs well with orchestrators like **Navigator** in Health Universe for managing clinical workflows and microservices at scale. This allows developers to leverage the power of Navigator to use clinical and workflow context to power their FastAPI apps.


# Typical Project Setup

Now that you have installed the required software, it's time to create a basic FastAPI project. In this section, we'll guide you through creating a new project, configuring your development tools, and setting up a basic API structure.

Because FastAPI apps can be more complex (and Health Universe is primarily a deployment, not a development environment), it is best to have a local development environment that is linked to your GitHub repository.

### Step 1: Create a GitHub Repository

Go to GitHub and create a new public repository. At this time, the repo must be public in order for Health Universe to be able to deploy your project.

### Step 2: Clone Your Repository or Use Our Template

1. **Clone Your Repository**
   * `git clone https://github.com/<your_account>/<your_repo_name>.git`
2. **Use Our Template**
   * Start with our [FastAPI Template](https://github.com/Health-Universe/fastapi-template-demo), designed to work seamlessly with the Health Universe Navigator interface. It’s highly recommended, as it’s easy to customize and already optimized for deployment.
   * Click the green **“Use this template”** button (top right).
   * Select **“Create a new repository”**.
   * Give your new repository a name (e.g., `tool-bmi-calculator`).
   * Click **“Create repository from template”**.
   * Once it's created, **clone** your new repo to your local machine OR modify the script within Github.

### Step 3: Set Up a Virtual Environment

Setting up a virtual environment for your Health Universe projects is required as it creates the requirements.txt file needed to specify your application's dependencies. To create a virtual environment, follow these steps:

1. Open a terminal and navigate to your desired project directory.
2. Create a new virtual environment with the following command:

   ```css
   python -m venv .env
   ```
3. Activate the virtual environment:
   * **Windows**:

     ```
     .env\Scripts\activate
     ```
   * **macOS and Linux**:

     ```bash
     source .env/bin/activate
     ```

Once activated, your terminal should display the environment name, e.g., `(venv)`.

### Step 4: Install FastAPI, Uvicorn, etc.

FastAPI is a high-performance framework for building APIs. Uvicorn is the ASGI server used to run FastAPI apps.. To install them, run the following command in your terminal:

```
pip install fastapi uvicorn pydantic python-multipart httpx
```

Wait for the installation to complete. FastAPI, Uvicorn, etc is now installed on your system and ready for use in your Health Universe projects.

### Step 5: Edit App.py

Let's build a simple "Hello, World!" style API with input validation using **Pydantic**.

Open `main.py` in your editor and add:

```python
from fastapi import FastAPI
from pydantic import BaseModel

# Input schema using Pydantic
class GreetRequest(BaseModel):
    name: str

# Create the app
app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Welcome to Health Universe!"}

@app.post("/greet")
def greet_user(request: GreetRequest):
    return {"message": f"Hello, {request.name}!"}
```

Save the `main.py` file.

### Step 6: Run the FastAPI App

To run your "Hello, World!" FastAPI app, open the terminal, navigate to your project directory, and run the following command:

```arduino
uvicorn main:app --reload
```

Then open your browser and go to:

* <http://127.0.0.1:8000> to view the root endpoint.
* <http://127.0.0.1:8000/docs> to test your API using Swagger UI.
* To stop the app, press `Ctrl+C` in the terminal.

### Step 7: Run pip freeze

From your terminal, run:

```
pip freeze > requirements.txt
```

This command will save all your project's dependencies into a requirements.txt file. This file is read by the Health Universe cloud to know what libraries your app will require.

### Step 8: Commit Your Code and Push to GitHub

### Step 9: Deploy Your App on Health Universe


# Your First Health Universe App

With your development environment set up, it's time to create your first healthcare app using Health Universe and FastAPI. In this tutorial, we will guide you through building a simple healthcare data API that allows users to filter patient data and view visualizations through an interactive chart endpoint.

### Prerequisites

First, make sure your development environment is ready. Install the necessary libraries:

```
pip install fastapi uvicorn python-multipart kaleido
```

### Step 1: Prepare Your Data

For this tutorial, we'll use a sample dataset containing information about patients, such as age, gender, and health metrics. You can find a suitable dataset online or create a CSV file with the following columns: `Patient_ID`, `Age`, `Gender`, `Height`, `Weight`, `Blood_Pressure`, and `Heart_Rate`.

Save the CSV file as `patient_data.csv` in the `data` directory of your project.

### Step 2: Load the Data in Your App

To load the data in your app, we'll use the Pandas library. If you haven't installed Pandas, run the following command in your terminal:

```
pip install pandas
```

Next, open `app.py` in your text editor or IDE and import the Pandas library by adding the following line at the beginning of the file:

```python
import pandas as pd
```

Now, modify the `app.py` file to load the patient data. This load the dataset when the app starts so it’s available to all endpoints.

```python
from fastapi import FastAPI, Form
from typing import Annotated, Literal
from fastapi.responses import StreamingResponse
import pandas as pd
from io import BytesIO
import base64
import kaleido

app = FastAPI(
    title="Healthcare Data API",
    description=(
    "A simple healthcare data API that allows users to filter patient data and view visualizations through an interactive chart endpoint."
    ),
    version="1.0.0",
)

# Load the patient dataset on startup
df = pd.read_csv("data/patient_data.csv")
```

Save the `app.py` file.

### Step 3: Create Endpoints for Filtering Data

To allow users to filter the data based on age and gender, add your first endpoint: an API route that filters the data by age and gender:

```python
@app.post("/filter-patients")
def filter_patients(
    min_age: Annotated[int, Form()],
    max_age: Annotated[int, Form()],
    gender: Annotated[Literal["Male", "Female", "All"], Form()] = "All"
):
    filtered = df[(df["Age"] >= min_age) & (df["Age"] <= max_age)]
    if gender != "All":
        filtered = filtered[filtered["Gender"] == gender]

    return filtered.to_dict(orient="records")
```

This endpoint allows users to:

* Set a minimum and maximum age
* Choose to filter by gender (Male, Female, or All)

### Step 4: Add an Endpoint to Visualize the Data

To visualize the filtered data, we'll create a bar chart showing the average blood pressure for each age group. First, install the Plotly library by running the following command in your terminal:

Next, import the Plotly library in `app.py` by adding the following line at the beginning of the file:

```python
import plotly.express as px
```

Now, we’ll add a `/chart` endpoint that displays a bar chart showing the average blood pressure by age:

```python
@app.post("/chart")
def blood_pressure_chart(
    min_age: Annotated[int, Form()],
    max_age: Annotated[int, Form()],
    gender: Annotated[Literal["Male", "Female", "All"], Form()] = "All"
):
    filtered = df[(df["Age"] >= min_age) & (df["Age"] <= max_age)]
    if gender != "All":
        filtered = filtered[filtered["Gender"] == gender]

    if filtered.empty:
        return {"error": "No data found for given filters."}

    # Aggregate data by age
    agg = filtered.groupby("Age")["Blood_Pressure"].mean().reset_index()

    # Create a bar chart
    fig = px.bar(
        agg,
        x="Age",
        y="Blood_Pressure",
        text="Blood_Pressure",
        labels={"Blood_Pressure": "Average Blood Pressure"},
        title="Average Blood Pressure by Age",
        height=400
    )

    # Save chart to in-memory buffer
    buffer = BytesIO()
    fig.write_image(buffer, format="png")
    buffer.seek(0)

    return StreamingResponse(buffer, media_type="image/png")
```

This code calculates the average blood pressure for each age group and creates a bar chart using Plotly Express.

Save the `app.py` file.

### Step 5: Run Your Healthcare App

To run your healthcare data visualization app, open the terminal, navigate to your project directory, and run the following command:

```arduino
uvicorn app:app --reload
```

Open your browser and visit:

* `http://127.0.0.1:8000` — Launch page
* `http://127.0.0.1:8000/docs` — Interactive Swagger UI

Congratulations! You have successfully created your first healthcare app using FastAPI that you can deploy to Health Universe and share with the world. You can now build on this foundation to create more advanced applications, incorporating machine learning models, additional visualizations, and user interactivity.


# Navigator FastAPI best practices

Navigator is an AI-driven workflow automation tool that uses clinical, workflow and organizational context to anticipate tools and resources that can speed up administrative and clinical tasks. Once an app is identified and selected by the user, Navigator uses that context to autofill relevant fields.

Use the [**FastAPI template**](https://github.com/Health-Universe/fastapi-template-demo) for a standardized, easy-to-implement structure to create apps that integrate smoothly with Navigator. They are designed to help you define clear, context-aware inputs and outputs, and to enhance autofill precision.

#### **Describe your app accurately**

Navigator uses the app’s title, description, and keywords to identify highly relevant matches. Make sure to include:

* What the app is intended to do
* Expected inputs and outputs
* Best use cases within a clinical or administrative workflow
* Tasks that typically occur before and after your app is used

#### **Be as explicit with the input parameters as possible**

Use dropdown lists whenever possible.

* For example:
  * A dropdown with "Yes" or "No" is preferable to a free-text input where users type "yes" or "no."
  * For structured data like cancer staging, use dropdowns such as "Stage I, II, III, IV" or break down staging into components (e.g., stage, metastases presence).

#### **Separate different concepts into separate input parameters**

Each parameter should:

* Have a clear, descriptive label
* Represent a single type of data

For example, instead of a single field for "medications, problems, and allergies," create three separate fields. This improves autofill accuracy and clarity for users.

#### Minimum Quality Bars for Deploying an Application to Navigator

Before publishing your app to the Navigator environment, ensure it meets the following quality standards:

* **Timely Responses:** Your app should return a result within **15 seconds** to keep the workflow efficient. If it takes longer than this, then you may get a time out or navigator error.
* **Concise Output:** Keep the response brief. If the output is verbose, use an LLM to **summarize** it before returning.
* **Concurrency Support:** Your app must handle **multiple user requests simultaneously** — use techniques like unique IDs for ephemeral data or asynchronous operations.
* **Well-defined Metadata:** Clearly define all fields, functions, titles, descriptions, and metadata. This ensures Navigator’s LLM can accurately surface and suggest your app in the appropriate workflow contexts.

#### **Help improve Navigator**

Navigator is still learning. If you notice unexpected behavior with input parameters or autofill suggestions, please report them so we can continue to improve the experience.


# Working in React

The React runtime lets you **bring your own frontend** to Health Universe. Instead of using a generated Streamlit or Navigator UI, you build a fully custom single-page application (or server-rendered app) in React, Next.js, or any Node-based framework, and deploy it on Health Universe alongside everything else.

To talk to the platform, your app uses the **`@health-universe/react`** SDK — a typed set of React Query hooks and a ready-wired API client for the clinical surface of the Health Universe API: routines, documents, patients, sources/bundles, chat threads, and clinical tools/connectors. You write your UI; the SDK handles auth and data fetching.

This is the preferred path for building **real, production clinical products** where you need full control over the user experience.

## What the SDK is for

`@health-universe/react` gives clinical customers typed React Query hooks and a ready-wired client for the Health Universe API, so you can build your own UI instead of using the platform's. When your app runs on Health Universe, it works with the **signed-in user's Health Universe account** automatically — the SDK handles authentication for you, so there are no separate logins or tokens to manage.

The client is **deliberately scoped** — only the clinical domains below are generated. The rest of the platform API (admin, deployments, billing, internal tooling) does not exist in this package and cannot be called through it.

| Area                   | What it covers                                                         |
| ---------------------- | ---------------------------------------------------------------------- |
| **Routines**           | Routines, routine runs, triggers, connector bindings                   |
| **Documents**          | Search (structured / semantic / text), status, chunks, export, bundles |
| **Clinical data**      | Patients, providers, rosters, appointments                             |
| **Sources**            | Document bundles + detail (input docs, patient, runs)                  |
| **Chat**               | Navigator threads (where context documents live)                       |
| **Tools & connectors** | Clinical tools, connectors                                             |

## Supported frameworks

All three are supported and auto-detected at deploy time from your `package.json`:

* **React** (e.g. Vite) — built as a **static SPA**. Health Universe runs your build step and serves the static assets.
* **Next.js** — runs as a **Node server** via `npm start`.
* **Node** — any other Node-based server app that exposes a `start` script, also run via `npm start`.

## When to use React

Use the React runtime when you want a **standalone app with a full custom UI** — a product, not a single calculator or a Navigator tool. You get the complete clinical data surface through typed hooks, client-side routing, your own design system, and the ability to compose routines, documents, and patient data into purpose-built workflows.

If you only need a quick Python tool or prototype, Streamlit is faster to stand up; if you need a deterministic Python calculator that surfaces inside Navigator, use FastAPI; if you're building a reusable clinical agent or workflow, use A2A. See [Streamlit vs FastAPI vs A2A vs React](/overview/building-apps-in-health-universe/developing-your-health-universe-app/streamlit-vs-fastapi-vs-a2a) for the full comparison.

## Next steps

* [Your First Health Universe App](/overview/building-apps-in-health-universe/developing-your-health-universe-app/working-in-react/your-first-health-universe-app) — install the SDK, wire up the provider, and render a patient list.
* [Typical Project Setup](/overview/building-apps-in-health-universe/developing-your-health-universe-app/working-in-react/typical-project-setup) — repo layout, build & run, and how the runtime is auto-detected.
* [SDK Reference](/overview/building-apps-in-health-universe/developing-your-health-universe-app/working-in-react/sdk-reference) — the hooks by domain, plus the `useApiQuery` / `useApiMutation` escape hatch.

🔗 **Resources**

* `@health-universe/react` on npm: `npm install @health-universe/react @tanstack/react-query react`
* [`react-starter`](https://github.com/Health-Universe/react-starter) — a complete example app (provider wiring, dev proxy, a tabbed app with a run-review reader).


# Typical Project Setup

A Health Universe React app is an ordinary Node project — there's nothing special about the layout beyond installing the SDK and wiring a few build-time settings so it serves correctly behind a path prefix. The [`react-starter`](https://github.com/Health-Universe/react-starter) repo is a complete worked example; clone it as a starting point.

### Step 1: Create a GitHub Repository

Push your app to a GitHub repository. As with the other runtimes, the repo must be reachable by Health Universe to deploy it.

### Step 2: Repo layout

A typical Vite-based React app looks like:

```
my-clinical-app/
├── package.json          # dependencies + build/start scripts (drives runtime detection)
├── vite.config.js        # base path + optional dev proxy
├── index.html
└── src/
    ├── main.tsx          # mounts <HUProvider> + your app
    ├── HUProvider.tsx     # wraps HealthUniverseProvider (see "Your First App")
    └── …                  # your components, using the SDK hooks
```

`package.json` is the file the platform inspects to identify the runtime. A React (Vite) SPA only needs a `build` script — the platform serves the build output for you, so no `start` script is required:

```json
{
  "name": "my-clinical-app",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  },
  "dependencies": {
    "@health-universe/react": "^0.1.0",
    "@tanstack/react-query": "^5.0.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  }
}
```

> A **Next.js or Node** app instead needs a `start` script that listens on the port the platform provides (`PORT`, set to `8501`) — e.g. `"start": "next start -p $PORT"`.

### Step 3: Build & run — static SPA vs Node server

How your app runs on Health Universe depends on the framework, and the platform picks the right mode automatically:

* **React (e.g. Vite)** — built as a **static SPA**. Health Universe runs your `build` step, then serves the static output (`dist/` or `build/`) with a single-page-app fallback. There is no long-running server process, and a `start` script, if present, is ignored.
* **Next.js / Node** — run as a **Node server**. After the build, Health Universe runs `npm run start`, so your `package.json` must expose a `start` script that listens on `PORT` (`8501`). Next.js apps also need `basePath` configured (see Step 5).

### Step 4: Runtime auto-detection

When you deploy, Health Universe inspects your repository and **auto-detects the runtime from `package.json`** (the deploy "main file" is `package.json`; in a monorepo it's `<your-app-folder>/package.json`). You don't choose React/Next.js/Node from a dropdown — the dependencies decide:

* a `next` dependency → **Next.js** (Node server),
* otherwise a `react` / `react-dom` dependency → **React** (static SPA),
* otherwise → a plain **Node** server.

This determines whether the app is served as static assets or run as a Node server.

### Step 5: Base-path handling

Deployed apps are served under a **path prefix**, e.g. `https://apps.healthuniverse.com/<slug>/`, not a dedicated subdomain. For assets and client-side routing to resolve correctly, your build must know that prefix.

The platform injects the prefix at build time under several names so each framework can read it — `VITE_BASE_PATH` (Vite), `NEXT_PUBLIC_BASE_PATH` (Next.js `basePath`), `PUBLIC_URL` (CRA), and a bare `HU_BASE_PATH`. Wire the right one into your build. For Vite:

```js
// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  base: process.env.VITE_BASE_PATH || '/',
  // …plugins, etc.
})
```

#### Client-side routing: strip the trailing slash

If you use a client-side router (React Router, etc.), point its `basename` at the same prefix — **but strip the trailing slash first.** Vite's `base` keeps the trailing slash (`/<slug>/`) because that's correct for asset URLs, but React Router treats a `basename` of `/<slug>/` as *not* matching the no-trailing-slash landing URL `/<slug>` — so the app mounts to a **blank page** even though every asset loaded fine. Strip it so `/<slug>`, `/<slug>/`, and `/<slug>/...` all match:

```tsx
// React Router's basename must NOT carry a trailing slash (Vite's `base` does,
// for assets). Strip it; the `|| '/'` collapse keeps local dev (no prefix) working.
const base = (import.meta.env.VITE_BASE_PATH || '/').replace(/\/+$/, '') || '/'

<BrowserRouter basename={base}>{/* …routes… */}</BrowserRouter>
```

> This is the most common blank-page-after-deploy cause **once you add routing**: assets resolve (correct `base`), but no route matches the landing URL. Don't use `import.meta.env.BASE_URL` for the basename directly — it carries the same trailing slash. Any in-app URLs you build by hand should still use the un-stripped `BASE_URL` so asset paths stay correct.

> When deployed, Health Universe also injects the API origin and account settings (e.g. `VITE_HU_API_URL`, `VITE_CLERK_PUBLISHABLE_KEY`) at build time, so the SDK is configured for you and the signed-in user is already authenticated — no secrets to manage in your repo for the default deployed setup.

### Step 6: Local development against a remote API

To develop against a remote API (staging or a preview) without running a local backend — and without CORS headaches — proxy `/api` through your dev server and point the SDK at the same origin. With Vite:

```js
// vite.config.js
import { defineConfig, loadEnv } from 'vite'

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), '')
  return {
    base: process.env.VITE_BASE_PATH || '/',
    server: {
      proxy: {
        '/api': { target: env.VITE_HU_API_PROXY, changeOrigin: true, secure: true },
      },
    },
  }
})
```

Then set `VITE_HU_API_URL=` (empty → same-origin) and `VITE_HU_API_PROXY=https://<staging-host>`, and sign in with the matching Clerk **development** instance (`pk_test_…`).

> If you reinstall the SDK and an import looks stale (`does not provide an export named …`), clear the Vite optimize cache (`rm -rf node_modules/.vite`) and restart the dev server.

### Step 7: Commit and deploy

Commit your code, push to GitHub, then deploy via **Create → Source Code**. See [Deploying to Health Universe](/overview/building-apps-in-health-universe/deploying-your-app-to-health-universe/deploying-to-health-universe) for the step-by-step deploy flow.


# Your First Health Universe App

This tutorial walks through building a minimal clinical frontend on Health Universe with React and the `@health-universe/react` SDK. By the end you'll have an app that authenticates against the Health Universe API and renders a list of patients.

For a complete, runnable example — provider wiring, a local dev proxy, and a tabbed app with a run-review reader — clone the [`react-starter`](https://github.com/Health-Universe/react-starter) repo as a starting point.

### Step 1: Install the SDK

```bash
npm install @health-universe/react @tanstack/react-query react
```

`react` and `@tanstack/react-query` are peer dependencies, so they live in your app. You do **not** install `@clerk/clerk-react` — the SDK manages Clerk internally (requires `@health-universe/react` ≥ 0.2.2).

### Step 2: How it works with your Health Universe account

You don't build a login or manage sessions. When your app runs on Health Universe, the user is **already signed in to their Health Universe account**, and the SDK authenticates every API request as that user automatically. You just give the provider a way to read the current account, and the platform takes care of the rest.

### Step 3: Set up `HealthUniverseProvider`

Wrap your app once near the root. `HealthUniverseProvider` wires up the authenticated client and a React Query `QueryClient` (pass your own via `queryClient`), and **manages Clerk for you** — it loads Clerk under the hood and authenticates every request as the signed-in user. You never install or import `@clerk/clerk-react`.

```tsx
import { HealthUniverseProvider } from '@health-universe/react'

// API origin WITHOUT the /api/v1 suffix (the SDK adds it). On Health Universe the
// API is served same-origin with your app, so there's no CORS. `??` (not `||`) so
// an explicit '' is respected — '' means same-origin (the dev proxy; see Typical Setup).
const API_URL = import.meta.env.VITE_HU_API_URL ?? 'https://api.healthuniverse.com'

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <HealthUniverseProvider
      baseUrl={API_URL}
      // The SDK self-loads Clerk with this key. On Health Universe it's injected
      // automatically; passing it from your env makes the same code work locally too.
      publishableKey={import.meta.env.VITE_CLERK_PUBLISHABLE_KEY}
    >
      {children}
    </HealthUniverseProvider>
  )
}
```

> **On Health Universe you can omit `publishableKey` entirely** — the platform injects the key and the SDK reads it, so `<HealthUniverseProvider baseUrl={API_URL}>` alone is enough and the user is already signed in. Pass it from `VITE_CLERK_PUBLISHABLE_KEY` (a `pk_test_…` key in `.env.local`) so the *same code* also runs in local dev, where there's no injected key.

The provider props:

| Prop                         | Required | Purpose                                                                                                                                               |
| ---------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `baseUrl`                    | Yes      | API origin **without** `/api/v1` (the SDK adds it). From the injected `VITE_HU_API_URL`; an explicit `''` means same-origin (no CORS).                |
| `publishableKey`             | No       | Clerk key the SDK self-loads with. Defaults to the platform-injected key on Health Universe; pass it from `VITE_CLERK_PUBLISHABLE_KEY` for local dev. |
| `organizationId`             | No       | Override the active workspace for org-scoped hooks. Defaults to the signed-in user's active org.                                                      |
| `tokenTemplate`              | No       | Clerk JWT template for the bearer. Defaults to `'app-session'` (carries the `org_id` org-scoped endpoints require).                                   |
| `getToken` / `clerk={false}` | No       | Escape hatch: bring your own token / Clerk instance (e.g. an app already running `@clerk/clerk-react`).                                               |
| `queryClient`                | No       | Your own React Query client. If omitted, the provider creates one.                                                                                    |

> **No `@clerk/clerk-react`.** The SDK bundles and lazy-loads Clerk itself, so sign-in state, the active organization (and its slug), and sign-in/out/org-switching are all available through the SDK — see [`useHealthUniverseAuth`](/overview/building-apps-in-health-universe/developing-your-health-universe-app/working-in-react/sdk-reference) and the `<SignedIn>` / `<SignedOut>` / `<OrganizationSwitcher>` / `<UserButton>` components. For a quick off-platform smoke test with no Clerk, pass `getToken={async () => import.meta.env.VITE_HU_TOKEN || null}` instead.

### Step 4: Render a patient list

With the provider in place, any component below it can use the SDK's hooks. Here's a minimal searchable patient roster:

```tsx
import { useState } from 'react'
import { usePatients, useHealthUniverse } from '@health-universe/react'

function PatientList() {
  const { organizationId } = useHealthUniverse()
  const [search, setSearch] = useState('')

  const { data, isFetching } = usePatients(
    { organizationId, search: search || undefined, limit: 50 },
    { enabled: !!organizationId },
  )

  return (
    <>
      <input
        value={search}
        onChange={(e) => setSearch(e.target.value)}
        placeholder="Search patients…"
      />
      {isFetching && <p>Loading…</p>}
      {data?.patients.map((p) => (
        <div key={p.id}>
          {p.firstName} {p.lastName} · {p.birthDate}
        </div>
      ))}
    </>
  )
}
```

`useHealthUniverse()` exposes the provider's context (including the active `organizationId`). Org-scoped hooks like `usePatients` take the org id as a query param and accept the standard React Query options as a second argument (`enabled`, `staleTime`, and so on).

### Step 5: Workspaces — one shared, or each visitor's

When you deploy (on the **Privacy & access** step), you choose how the app is bound to a workspace. The SDK handles both modes for you:

* **One shared workspace (pinned)** — the app always works inside the workspace you deployed it into. Everyone who opens it sees that same workspace's data. The SDK activates that workspace automatically; there's nothing to switch.
* **Each visitor's workspace** — the app works inside whoever's using it's own active workspace, just like the main Health Universe app. Different people see their own data, and they can switch between the workspaces they belong to.

You usually don't need to do anything special — `usePatients`, `useRoutines`, etc. already operate in the active workspace. If you want to **let people switch workspaces** (only meaningful in the "each visitor's workspace" mode), render the SDK's `<OrganizationSwitcher>` and gate it on the mode the SDK reports via `useWorkspace()`:

```tsx
import { OrganizationSwitcher, useWorkspace } from '@health-universe/react'

function WorkspaceBar() {
  const { isPinned } = useWorkspace()
  // A pinned app is locked to one workspace — no switcher needed.
  if (isPinned) return null
  return <OrganizationSwitcher />
}
```

The SDK's `<OrganizationSwitcher>` renders Clerk's prebuilt widget (via clerk-js, no `@clerk/clerk-react`). Switching the active workspace updates the SDK's resolved org, and its hooks automatically refetch for the new workspace. Need the active workspace's **slug** (for `useSources` / `useBundleDetail`)? Read it from `useHealthUniverseAuth().organization?.slug`.

### Step 6: Deploy

Push your repo to GitHub, then **Create → Source Code** in Health Universe and select it. The runtime auto-detects as **React** (main file `package.json`), the platform runs your build, and the app is served under a path prefix. See [Typical Project Setup](/overview/building-apps-in-health-universe/developing-your-health-universe-app/working-in-react/typical-project-setup) for base-path handling and runtime detection, and [Deploying to Health Universe](/overview/building-apps-in-health-universe/deploying-your-app-to-health-universe/deploying-to-health-universe) for the full deploy flow.

Congratulations — you've built your first custom clinical frontend on Health Universe. From here, explore the [SDK Reference](/overview/building-apps-in-health-universe/developing-your-health-universe-app/working-in-react/sdk-reference) to wire up routines, documents, sources/bundles, and chat threads.


# SDK Reference

`@health-universe/react` exposes typed React Query hooks grouped by clinical domain, plus a generic escape hatch (`useApiQuery` / `useApiMutation` / `api`) for any scoped operation that doesn't yet have a first-class hook.

Every hook's arguments and return type are inferred from the generated client, so the bundled TypeScript declarations (`@health-universe/react` ships `.d.ts`) are the canonical source of truth — your editor's autocomplete is the fastest reference.

> **Org id vs workspace slug.** Two different identifiers address the organization, and mixing them up is the most common mistake. Both come from `useHealthUniverseAuth().organization` (see [Auth](#auth)):
>
> * **Org id** (`org_…`, `organization.id`) — used by org-scoped hooks as a query param: `usePatients({ organizationId })`, `useRoutines(organizationId)`, `useOrgRoutineRuns(organizationId)`. (Org-scoped hooks also default to the provider's active org, so you often don't pass it.)
> * **Workspace slug** (e.g. `acme-health`, `organization.slug`) — used by workspace-scoped hooks as a path param: `useSources(slug)`, `useBundleDetail(slug, bundleId)`.
>
> If a workspace-scoped hook returns nothing, check the slug and your org membership first.

***

## Auth

The SDK manages Clerk internally — **you never import `@clerk/clerk-react`**. It loads Clerk's official script from Clerk's CDN at runtime (the same way `@clerk/clerk-react` does), so the prebuilt widgets are fully styled. Read sign-in state and the active organization, and trigger sign-in/out/org-switching, through the SDK.

```tsx
import { useHealthUniverseAuth, SignedIn, SignedOut, OrganizationSwitcher, UserButton } from '@health-universe/react'

function Header() {
  const { isSignedIn, user, organization } = useHealthUniverseAuth()
  return (
    <header>
      <OrganizationSwitcher />   {/* Clerk's prebuilt widget, via clerk-js */}
      <UserButton />
    </header>
  )
}

// Gate UI on sign-in state:
<SignedIn><App /></SignedIn>
<SignedOut><SignInScreen /></SignedOut>
```

`useHealthUniverseAuth()` returns:

* `isLoaded`, `isSignedIn`
* `user` — the signed-in user (id + display fields)
* `organization` — the active org as `{ id, slug, name }` (the source of both the org **id** and the workspace **slug** above), or `null`
* `organizations` — the user's org memberships, for a custom switcher
* `signIn()`, `signOut()`, `setActiveOrg(organizationId)`

Components: `<SignedIn>` / `<SignedOut>` gate on sign-in state; `<OrganizationSwitcher>` / `<UserButton>` render Clerk's prebuilt, styled widgets (use [`useWorkspace().isPinned`](/overview/building-apps-in-health-universe/developing-your-health-universe-app/working-in-react/your-first-health-universe-app) to hide the switcher in a pinned app). See [Your First App](/overview/building-apps-in-health-universe/developing-your-health-universe-app/working-in-react/your-first-health-universe-app) for provider setup.

> **Locked by platform policy.** These widgets are pinned to a deployed app's scope and **can't be unlocked by props**: `<OrganizationSwitcher>` is switch-only (no personal workspace, no Create/Manage organization) and `<UserButton>` hides "Manage account" (Sign out stays). The org **slug** for workspace-scoped hooks still comes from `useHealthUniverseAuth().organization.slug`. The hiding is a UI lock — for an airtight boundary, also disable org creation and restrict the manage/account permissions in the Clerk Dashboard.

***

## Patients

```tsx
import { usePatients, usePatient, useCreatePatient } from '@health-universe/react'

function Roster({ orgId }: { orgId: string }) {
  const { data, isLoading } = usePatients({ organizationId: orgId, sortBy: 'last_name' })
  const create = useCreatePatient()
  // create.mutate({ body: { organization_id: orgId, first_name: 'Jane', … } })
}
```

The full set:

* `usePatients(params, options)` — list / search the org's patients
* `usePatient(id)` — a single patient
* `useCreatePatient()`, `useUpdatePatient()`, `useDeletePatient()`
* `useUpsertPatientByExternalId()` — idempotent EHR sync, keyed by your external id

***

## Documents

```tsx
import { useDocumentSearch, useDocumentStatus, useDocumentChunks } from '@health-universe/react'

const { data } = useDocumentSearch({ body: { query: 'discharge summary' } })
const { data: status } = useDocumentStatus(documentId)
```

The full set:

* `useDocumentSearch()` — structured search
* `useSemanticDocumentSearch()` — semantic search (e.g. `{ body: { query: 'aml relapse', threadId } }`)
* `useDocumentTextSearch()` — text search
* `useDocumentStatus(id)`, `useDocumentStatuses()` — status by document, or by thread/patient
* `useDocumentChunks(id)` — a document's extracted text, returned as ordered chunks
* `useExportDocument()`

Render a document's text by sorting its chunks:

```tsx
function DocumentText({ documentId }: { documentId: string }) {
  const { data } = useDocumentChunks(documentId)
  const text = (data?.chunks ?? [])
    .sort((a, b) => a.chunkIndex - b.chunkIndex)
    .map((c) => c.content)
    .join('\n\n')
  return <pre>{text}</pre>
}
```

***

## Routines & runs

```tsx
import { useRoutines, useTriggerRoutine, useRoutineRun } from '@health-universe/react'

const { data: routines } = useRoutines()   // org from the provider default
const trigger = useTriggerRoutine()         // trigger.mutate({ routineId, body: { … } })
const { data: run } = useRoutineRun(runId)  // polls every 3s, stops at a terminal status
```

The full set:

* `useRoutines(orgId)`, `useRoutine(id)` — list/read routines (name, description, `steps[]` pipeline, `inputs[]`)
* `useCreateRoutine()`, `useUpdateRoutine()`, `useDeleteRoutine()`, `useDiscoverRoutines()`
* Routine-secret hooks
* `useTriggerRoutine()` — start a run; returns the new `run_id`
* `useRoutineRun(runId)` — a single run, with **terminal-aware polling** (default 3s, stops at a terminal status)
* `useRoutineRuns(routineId)`, `useOrgRoutineRuns(orgId)` — run history
* `useRetryRun()`, `useCreateRunThread()`, `useDeleteRunThread()`

A run's generated documents live on each step's `output_artifacts` as an A2A artifact array — `[{ name, parts: [{ kind: 'text', text }] }]`:

```tsx
function runOutputs(run) {
  return (run?.steps ?? []).flatMap((step) => {
    const arr = Array.isArray(step.output_artifacts) ? step.output_artifacts : []
    return arr.map((a) => ({
      name: a.name,
      text: (a.parts ?? []).map((p) => p.text).filter(Boolean).join('\n\n'),
    }))
  })
}
```

> Generated text often embeds citation tags like `<file name="x.pdf">x.pdf</file>`. `react-markdown` won't render raw HTML, so strip or transform those before rendering.

***

## Sources (bundles) & run review

`useSources` and `useBundleDetail` are **workspace-scoped** — they take the org **slug** as a path param, not the org id.

```tsx
import { useSources, useBundleDetail } from '@health-universe/react'

// workspaceSlug is the org SLUG — useHealthUniverseAuth().organization?.slug
const { data: bundles } = useSources(workspaceSlug)
const { data: bundle } = useBundleDetail(workspaceSlug, bundleId)
// bundle.documents → input files, bundle.patient, bundle.runs
```

A routine **run** carries its source bundle on `trigger_context.bundle_id`. To build a full run-review surface, pair:

* `useRoutineRun(id)` — steps + `output_artifacts` (the generated documents), and
* `useBundleDetail(slug, bundleId)` — the input documents + patient the run started from.

See the [`react-starter`](https://github.com/Health-Universe/react-starter) `RunView` for a worked example.

***

## Chat threads

Navigator threads are where context documents live.

```tsx
import { useThreads, useThread } from '@health-universe/react'

const { data } = useThreads({ patientId, organizationId }) // search / list
```

The full set: `useThreads`, `useThread`, `useCreateThread`, `useUpdateThread`, `useDeleteThread`.

***

## Other clinical domains

These domains are available as hooks and/or through the escape hatch below:

* **Providers** — `useProviders`, `useProvider`, `useCreateProvider`, `useUpsertProviderByNpi`
* **Rosters** — `useRosters`, `useRoster`, `useRosterPatients`, …
* **Appointments** — `useAppointments`, …
* **Connectors** — `useEnabledConnectors`
* **Clinical tools** — `useAutofillForm`, `useExecuteTool`, `useGenerateFormSchema`, `useSuggestTools`

***

## Any endpoint, fully typed — `useApiQuery` / `useApiMutation`

Every scoped operation can be turned into a typed query or mutation without a bespoke hook. Domains that don't yet have first-class hooks (providers, rosters, appointments, clinical tools, connectors, bundles) are reachable this way today. Options and the returned data type are inferred from the operation.

```tsx
import { useApiQuery, useApiMutation, api } from '@health-universe/react'

// Query — options and the returned type are inferred from the operation.
function Providers({ orgId }: { orgId: string }) {
  const { data } = useApiQuery(
    api.getProviders,
    { query: { organizationId: orgId } },
    { enabled: !!orgId },
  )
}

// Mutation — variables are the operation's call options.
function NewAppointment() {
  const create = useApiMutation(api.createAppointment)
  // create.mutate({ body: { organization_id, provider_id, patient_id, start_time, … } })
}
```

`api` is the generated SDK for the scoped surface only. You can also grab the raw client with `useHealthUniverseClient()` and call `api.*` imperatively.

***

## Invalidating queries & realtime

Query-key factories are exported so you can invalidate after a mutation or an out-of-band update: `patientKeys`, `runKeys`, `documentKeys`, `sourceKeys`, `threadKeys`, and so on.

```tsx
import { useQueryClient } from '@tanstack/react-query'
import { runKeys } from '@health-universe/react'

const qc = useQueryClient()
qc.invalidateQueries({ queryKey: runKeys.detail(runId) })
```

For run progress, `useRoutineRun` already polls (default 3s) and stops at a terminal status. The SDK does **not** bundle Supabase — for sub-second updates, supply your own subscription and invalidate the relevant query (`runKeys.detail(runId)`, `patientKeys.list(...)`, etc.).

***

## Scope

The client is intentionally limited to the clinical domains above (routines, documents, patients, providers, rosters, appointments, sources/bundles, chat threads, clinical tools, connectors). Admin, deployments, billing, and internal tooling are **not** part of this package and cannot be called through it.


# Working with A2A Agents

A2A (Agent-to-Agent) agents are a new class of applications on Health Universe designed for AI-driven workflow automation. Unlike Streamlit and FastAPI apps that require direct user interaction, A2A agents can communicate with each other and operate autonomously within Navigator workflows, making them ideal for orchestrating complex healthcare processes.

## What are A2A Agents?

A2A agents are conversational AI applications that:

* **Process natural language messages** and return structured responses
* **Communicate with other agents** to complete complex tasks
* **Run autonomously** as part of Navigator's AI-driven workflows
* **Handle document operations** like reading clinical files, generating reports, and updating records
* **Provide progress updates** during long-running tasks

Think of A2A agents as specialized AI assistants that can be chained together to automate entire clinical or administrative workflows.

## A2A vs. Streamlit vs. FastAPI

| Feature                     | **A2A Agents**                 | **Streamlit**                   | **FastAPI**             |
| --------------------------- | ------------------------------ | ------------------------------- | ----------------------- |
| **Primary Use**             | AI workflow automation         | Interactive dashboards          | API services            |
| **User Interaction**        | Natural language messages      | Direct UI interaction           | HTTP requests           |
| **Autonomy**                | Fully autonomous               | User-driven                     | Request-response        |
| **Navigator Integration**   | Native AI workflows            | Standalone apps                 | Tool orchestration      |
| **Document Access**         | Built-in file operations       | Manual file handling            | API-based access        |
| **Inter-app Communication** | Native agent-to-agent          | External integrations           | API calls               |
| **Best For**                | Clinical workflows, automation | Data visualization, prototyping | Backend services, tools |

## When to Use A2A Agents

**✅ Use A2A Agents when:**

* Building **AI-powered clinical workflows** that need to process multiple documents or data sources
* Creating **multi-step processes** that involve several specialized tasks (e.g., document analysis → risk assessment → report generation)
* Developing **autonomous assistants** that can operate without constant user input
* Building agents that need to **communicate with other AI agents** in a workflow chain
* Creating **background processing systems** for tasks that take more than a few seconds

**Example A2A Agent Use Cases:**

* **Clinical Documentation Assistant**: Processes uploaded clinical notes, extracts key findings, and generates structured summaries
* **Protocol Analyzer**: Reviews research protocols, identifies potential issues, and suggests improvements
* **Care Plan Generator**: Takes patient data and clinical guidelines to create personalized care plans
* **Quality Assurance Agent**: Reviews clinical documentation for completeness and compliance

## A2A Agents in Navigator Workflows

Navigator's AI engine can automatically:

1. **Identify relevant agents** based on the clinical context and user intent
2. **Chain multiple agents together** to complete complex workflows
3. **Pass data between agents** seamlessly
4. **Monitor progress** and handle errors across the entire workflow
5. **Present results** to users when the workflow completes

For example, a user might say "Analyze this patient's lab results and create a care plan," and Navigator could:

1. Route lab documents to a **Lab Analysis Agent**
2. Send the analysis to a **Risk Assessment Agent**
3. Have a **Care Plan Generator** create recommendations
4. Use a **Documentation Agent** to format the final report

## Key Features of A2A Agents

### **Document Operations**

A2A agents have built-in access to Health Universe's document system:

```python
# List documents in the thread
docs = await context.document_client.list_documents()

# Read document content
content = await context.document_client.download_text(doc.id)

# Write new documents
await context.document_client.write("Analysis Results", json.dumps(results))

# Search documents semantically
results = await context.document_client.semantic_search("patient symptoms")
```

### **Progress Updates**

Long-running agents can provide real-time progress updates:

```python
await context.update_progress("Analyzing documents...", progress=0.3)
await context.update_progress("Generating recommendations...", progress=0.7)
await context.update_progress("Complete!", progress=1.0)
```

### **Inter-Agent Communication**

Agents can call other agents programmatically:

```python
# Call another agent in the workflow
response = await self.call_agent("protocol-analyzer", message, context)
result = response.text
```

### **Background Processing**

A2A agents run as background jobs, allowing for:

* **Long-running tasks** (up to 1 hour by default)
* **Persistent progress tracking** stored in the database
* **User notifications** when tasks complete
* **Error handling** and recovery

## Getting Started with A2A Agents

A2A agents are built using the Health Universe A2A SDK, which provides:

* **Base agent classes** for different use cases
* **Built-in document operations** for file handling
* **Progress tracking** and user notifications
* **Agent-to-agent communication** helpers
* **Local development tools** for testing

To start building A2A agents, you'll use the `health-universe-a2a` Python SDK, which handles all the complexities of the A2A protocol and Navigator integration.

**Next Steps:**

* [Set up the A2A SDK](https://github.com/Health-Universe/docs/blob/main/building-apps-in-health-universe/developing-your-health-universe-app/working-with-a2a-agents/sdk-setup.md)
* [Create your first agent](https://github.com/Health-Universe/docs/blob/main/building-apps-in-health-universe/developing-your-health-universe-app/working-with-a2a-agents/your-first-a2a-agent.md)
* [Agent best practices](https://github.com/Health-Universe/docs/blob/main/building-apps-in-health-universe/developing-your-health-universe-app/working-with-a2a-agents/a2a-best-practices.md)

## The Future of Healthcare Automation

A2A agents represent the next evolution of healthcare applications—moving from manual, user-driven tools to intelligent, autonomous systems that can handle complex workflows end-to-end. They enable healthcare professionals to focus on patient care while AI handles routine documentation, analysis, and administrative tasks.

By building A2A agents, you're contributing to a future where clinical workflows are more efficient, consistent, and intelligent, ultimately leading to better patient outcomes and reduced clinician burnout.


# Getting Started

A2A (Agent-to-Agent) agents are intelligent, autonomous applications that can process messages, handle document operations, and communicate with other agents in the Health Universe platform. They're perfect for building AI-powered healthcare workflows, document processing pipelines, and clinical decision support tools.

This guide will walk you through creating, testing, and deploying your first A2A agent.

## Prerequisites

Before you begin, make sure you have:

* Python 3.10+ installed
* A [GitHub account](/overview/building-apps-in-health-universe/getting-started-with-health-universe/create-a-github-account)
* A [Health Universe account](/overview/building-apps-in-health-universe/getting-started-with-health-universe/create-a-health-universe-account) with [GitHub linked](/overview/building-apps-in-health-universe/getting-started-with-health-universe/link-your-github-account-to-your-health-universe-account)
* Basic familiarity with Python and async programming

## Installation

### 1. Create a New Repository

Create a new GitHub repository for your agent:

```bash
git clone https://github.com/<your_username>/<your_repo_name>.git
cd <your_repo_name>
```

### 2. Set Up Your Environment

Create and activate a virtual environment:

```bash
python -m venv .env
source .env/bin/activate  # On Windows: .env\Scripts\activate
```

### 3. Install the SDK

Install the Health Universe A2A SDK:

```bash
pip install health-universe-a2a
```

## Creating Your First Agent

### Basic Agent Structure

Create a file called `main.py` with your first agent:

```python
Either use 'from health_universe_a2a import AsyncAgent as Agent, BackgroundContext as AgentContext' or clarify that these are recommended aliases
import json

class MedicalDataAnalyzer(Agent):
    def get_agent_name(self) -> str:
        return "Medical Data Analyzer"

    def get_agent_description(self) -> str:
        return "Analyzes medical datasets and generates clinical insights"

    async def process_message(self, message: str, context: AgentContext) -> str:
        # Update progress for the user
        await context.update_progress("Starting analysis...", 0.1)

        # List documents in the thread
        docs = await context.document_client.list_documents()
        
        if not docs:
            return "No documents found to analyze. Please upload medical data files."

        # Process each document
        results = []
        for i, doc in enumerate(docs):
            await context.update_progress(
                f"Analyzing {doc.name}...", 
                (i + 1) / len(docs) * 0.8
            )
            
            # Read document content
            try:
                content = await context.document_client.download_text(doc.id)
                # Simple analysis example
                word_count = len(content.split())
                results.append({
                    "document": doc.name,
                    "word_count": word_count,
                    "size_bytes": len(content.encode('utf-8'))
                })
            except Exception as e:
                results.append({
                    "document": doc.name,
                    "error": f"Could not process: {str(e)}"
                })

        # Write results as a new document
        await context.document_client.write(
            "Analysis Results",
            json.dumps(results, indent=2),
            filename="analysis_results.json"
        )

        await context.update_progress("Analysis complete!", 1.0)
        
        return f"Successfully analyzed {len(docs)} documents. Results saved to analysis_results.json"

if __name__ == "__main__":
    agent = MedicalDataAnalyzer()
    agent.serve()
```

### Understanding the Agent Structure

Every A2A agent must implement three core methods:

* **`get_agent_name()`**: Returns the display name for your agent
* **`get_agent_description()`**: Describes what your agent does
* **`process_message()`**: The main logic that processes user requests

The `AgentContext` provides access to:

* **Document operations**: `context.document_client` for reading/writing files
* **Progress updates**: `context.update_progress()` for user feedback
* **Inter-agent communication**: `context.call_agent()` for calling other agents

## Testing Locally

### 1. Create Test Data

Create a local test environment:

```bash
mkdir test_data
mkdir test_data/source
echo "Patient ID: 12345, Age: 45, Diagnosis: Hypertension" > test_data/source/patient_data.txt
```

### 2. Local Testing Script

Create `test_local.py`:

```python
import asyncio
from health_universe_a2a.local import create_local_context
from main import MedicalDataAnalyzer

async def test_agent():
    # Create local context
    context = create_local_context("./test_data")
    
    # Create agent instance
    agent = MedicalDataAnalyzer()
    
    # Test the agent
    result = await agent.process_message(
        "Please analyze the medical data", 
        context
    )
    
    print("Agent result:", result)

if __name__ == "__main__":
    asyncio.run(test_agent())
```

Run the test:

```bash
python test_local.py
```

### 3. Test the Server

You can also test the full HTTP server locally:

```bash
python main.py
```

Then visit:

* <http://localhost:8000> - Main endpoint
* <http://localhost:8000/.well-known/agent-card.json> - Agent metadata
* <http://localhost:8000/health> - Health check

## Creating Requirements File

Generate your dependencies:

```bash
pip freeze > requirements.txt
```

Your `requirements.txt` should include at minimum:

```txt
health-universe-a2a>=0.2.0
```

## Deploying to Health Universe

### 1. Prepare Your Repository

Ensure your repository contains:

* `main.py` - Your agent code
* `requirements.txt` - Dependencies
* `README.md` - Documentation (optional but recommended)

### 2. Commit and Push

```bash
git add .
git commit -m "Initial A2A agent implementation"
git push origin main
```

### 3. Deploy on Health Universe

1. Go to your [Health Universe workspace](/overview/building-apps-in-health-universe/creating-a-workspace)
2. Click **"Deploy a new app"**
3. Fill in the deployment form:
   * **GitHub Repository**: Select your repository
   * **App Name**: "Medical Data Analyzer"
   * **Description**: "Analyzes medical datasets and generates clinical insights"
   * **Runtime**: Select **FastAPI** → **Add to Navigator**
   * **Main File**: `main.py`
4. Click **"Deploy App"**

### 4. Monitor Deployment

Watch the build logs to ensure your agent deploys successfully. Once complete, your agent will be available in Health Universe Navigator.

## Using Your Agent in Navigator

1. **Open Navigator** in your Health Universe workspace
2. **Upload documents** to a thread (medical data files, reports, etc.)
3. **Search for your agent** by name or description
4. **Click "Use Tool"** to run your agent
5. **Monitor progress** as your agent processes the documents
6. **View results** in the artifacts panel when complete

## Next Steps

Now that you have a basic agent working, you can:

* [Learn about document operations](/overview/building-apps-in-health-universe/developing-your-health-universe-app/working-with-a2a-agents/document-operations) for advanced file handling
* [Explore inter-agent communication](/overview/building-apps-in-health-universe/developing-your-health-universe-app/working-with-a2a-agents/inter-agent-communication) for building agent workflows
* [Add validation](https://github.com/Health-Universe/docs/blob/main/building-apps-in-health-universe/developing-your-health-universe-app/working-with-a2a-agents/validation-and-error-handling.md) to make your agents more robust
* [Integrate with LLMs](https://github.com/Health-Universe/docs/blob/main/deploying-your-app-to-health-universe/connecting-to-an-llm.md) for AI-powered analysis

## Common Issues

**Agent not appearing in Navigator?**

* Ensure you selected "Add to Navigator" during deployment
* Check that your agent implements all required methods

**Local testing not working?**

* Make sure you activated your virtual environment
* Verify the `test_data/source/` directory exists with sample files

**Deployment fails?**

* Check your `requirements.txt` includes `health-universe-a2a`
* Ensure your `main.py` file is in the repository root
* Review the build logs for specific error messages

**Need help?**

* Check the [Health Universe documentation](https://docs.healthuniverse.com)
* Review the [SDK source code](https://github.com/Health-Universe/health-universe-a2a-sdk) for examples


# Agent Types

The Health Universe A2A SDK provides two main agent types to fit different use cases and architectural patterns. Understanding when to use each type is crucial for building effective healthcare applications.

## Agent vs SubAgent

### Agent (AsyncAgent)

The primary agent class for Health Universe applications that handle long-running tasks with progress updates. `Agent` is an alias for the underlying `AsyncAgent` implementation.

**Key Characteristics:**

* **Background processing**: Tasks run asynchronously with progress updates stored in the database
* **SSE acknowledgment**: Immediate acknowledgment via Server-Sent Events, then background execution
* **Progress updates**: Real-time status updates visible in Navigator UI
* **Document operations**: Full access to document storage and retrieval
* **User-facing**: Designed for direct user interaction through Navigator

**Example Use Cases:**

* Clinical data analysis that takes several minutes
* Document processing and extraction workflows
* Machine learning model inference on large datasets
* Multi-step protocol generation
* Report generation with intermediate status updates

```python
from health_universe_a2a import Agent, AgentContext

class ClinicalAnalyzer(Agent):
    def get_agent_name(self) -> str:
        return "Clinical Data Analyzer"
    
    def get_agent_description(self) -> str:
        return "Analyzes clinical datasets and generates comprehensive reports"
    
    async def process_message(self, message: str, context: AgentContext) -> str:
        # Long-running analysis with progress updates
        await context.update_progress("Loading documents...", 0.1)
        docs = await context.document_client.list_documents()
        
        await context.update_progress("Processing data...", 0.5)
        results = await self.analyze_clinical_data(docs)
        
        await context.update_progress("Generating report...", 0.9)
        report = await self.generate_report(results)
        
        # Save results as document
        await context.document_client.write(
            "Analysis Report", 
            report, 
            filename="clinical_analysis.md"
        )
        
        return "Analysis complete! Report generated successfully."
```

### SubAgent

Lightweight agent for fast, inline processing typically called by other agents.

**Key Characteristics:**

* **Inline processing**: Returns results directly in HTTP response
* **No progress updates**: Processing is fast enough that users can wait
* **Inter-agent use**: Designed to be called by other agents or services
* **Simplified context**: Uses SubAgentContext with document access but no update mechanisms
* **Fast response**: Should complete within 30 seconds

**Example Use Cases:**

* Document format conversion
* Data validation and verification
* Quick calculations or transformations
* Extracting structured data from documents
* Format standardization between systems

```python
from health_universe_a2a import SubAgent, SubAgentContext
import json

class DocumentConverter(SubAgent):
    def get_agent_name(self) -> str:
        return "Document Format Converter"
    
    def get_agent_description(self) -> str:
        return "Converts documents between different medical formats"
    
    async def process_message(self, message: str, context: SubAgentContext) -> str:
        data = json.loads(message)
        
        # Quick format conversion
        doc = await context.document_client.get_document(data["document_id"])
        content = await context.document_client.download_text(doc.id)
        
        # Fast processing - no progress updates needed
        converted = await self.convert_format(content, data["target_format"])
        
        return json.dumps({
            "status": "success",
            "converted_content": converted,
            "format": data["target_format"]
        })
```

## Comparison Table

| Feature               | Agent (AsyncAgent)                 | SubAgent                |
| --------------------- | ---------------------------------- | ----------------------- |
| **Processing Style**  | Background/async                   | Inline/direct           |
| **Response Time**     | Minutes to hours                   | Seconds to \~30s        |
| **Progress Updates**  | ✅ Real-time updates                | ❌ No updates            |
| **User Interface**    | Navigator with progress bar        | Called by other agents  |
| **Document Access**   | ✅ Full operations                  | ✅ Read/write access     |
| **Inter-agent Calls** | ✅ Can call other agents            | ✅ Can call other agents |
| **Background Jobs**   | ✅ Database-persisted               | ❌ Not applicable        |
| **Context Type**      | `AgentContext` (BackgroundContext) | `SubAgentContext`       |
| **Streaming Support** | ✅ SSE acknowledgment               | ❌ Direct response       |

## Architectural Patterns

### Pattern 1: Orchestrator + Workers

Use an Agent as orchestrator with SubAgents as workers:

```python
# Orchestrator (Agent/AsyncAgent)
class ProtocolOrchestrator(Agent):
    async def process_message(self, message: str, context: AgentContext) -> str:
        await context.update_progress("Starting protocol generation...", 0.1)
        
        # Call SubAgent for document analysis
        analysis = await self.call_agent(
            "document-analyzer", 
            message, 
            context
        )
        
        await context.update_progress("Generating sections...", 0.5)
        
        # Call SubAgent for section writing
        sections = await self.call_agent(
            "section-writer",
            json.dumps(analysis),
            context
        )
        
        await context.update_progress("Finalizing protocol...", 0.9)
        return "Protocol generation complete!"

# Worker (SubAgent)  
class DocumentAnalyzer(SubAgent):
    async def process_message(self, message: str, context: SubAgentContext) -> str:
        # Fast document analysis
        docs = await context.document_client.list_documents()
        analysis = await self.analyze_documents(docs)
        return json.dumps(analysis)
```

### Pattern 2: Multi-Agent Workflow

Chain multiple Agents for complex workflows:

```python
# Deploy multiple agents at different paths
from health_universe_a2a import serve_multi_agents

serve_multi_agents({
    "/orchestrator": ProtocolOrchestrator(),
    "/document-processor": DocumentProcessor(),  # Agent (AsyncAgent)
    "/section-writer": SectionWriter(),          # Agent (AsyncAgent)
    "/format-converter": FormatConverter(),      # SubAgent
})
```

## When to Choose Each Type

### Choose Agent when:

* ✅ Processing takes more than 30 seconds
* ✅ Users need progress visibility
* ✅ Working with large datasets or documents
* ✅ Running machine learning inference
* ✅ Users interact directly through Navigator
* ✅ Need to generate substantial outputs or reports
* ✅ Process involves multiple steps that benefit from status updates

### Choose SubAgent when:

* ✅ Processing completes in under 30 seconds
* ✅ Called by other agents rather than users directly
* ✅ Performing simple transformations or validations
* ✅ Converting between data formats
* ✅ Extracting specific information from documents
* ✅ Acting as a microservice in larger workflows
* ✅ Providing utility functions to other agents

## Best Practices

### For Agents:

* Send meaningful progress updates every 10-20% of completion
* Use descriptive status messages that help users understand progress
* Handle long-running operations with proper error recovery
* Save intermediate results to documents when appropriate
* Implement cancellation checks for very long processes

### For SubAgents:

* Keep processing under 30 seconds to avoid timeouts
* Return structured JSON for easy consumption by calling agents
* Focus on single, well-defined responsibilities
* Use clear input/output schemas for inter-agent communication
* Handle errors gracefully with informative error messages

### For Both:

* Use descriptive agent names and descriptions for Navigator discovery
* Implement proper error handling and logging
* Follow healthcare data security best practices
* Test thoroughly with realistic data volumes
* Document expected inputs and outputs clearly

The choice between Agent and SubAgent should be driven by your use case requirements, expected processing time, and whether users need visibility into progress. Many sophisticated healthcare applications use both types working together in complementary roles.


# Inter-Agent Communication

The Health Universe A2A SDK provides powerful methods for agents to communicate with each other, enabling complex multi-agent workflows and orchestration patterns. This guide covers how to use `call_agent()` and `call_other_agent()` methods for seamless agent-to-agent communication.

## Overview

Inter-agent communication allows your A2A agents to:

* **Orchestrate workflows** by calling specialized agents for specific tasks
* **Chain processing steps** where one agent's output feeds into another
* **Distribute workload** across multiple specialized microservices
* **Leverage existing tools** without reimplementing functionality

The SDK provides two primary methods for agent communication:

* `call_agent()` - Unified method with automatic registry resolution
* `call_other_agent()` - Context-aware method with JWT propagation

## Basic Agent Communication

### Using `call_agent()`

The `call_agent()` method is the most flexible approach, supporting multiple identifier formats:

```python
from health_universe_a2a import Agent, AgentContext

class OrchestratorAgent(Agent):
    def get_agent_name(self) -> str:
        return "Document Orchestrator"

    def get_agent_description(self) -> str:
        return "Coordinates document processing workflow"

    async def process_message(self, message: str, context: AgentContext) -> str:
        # Call with agent name (registry lookup)
        analysis = await self.call_agent("document-analyzer", message, context)
        
        # Call with local path (same pod)
        summary = await self.call_agent("/summarizer", analysis, context)
        
        # Call with direct URL
        validation = await self.call_agent(
            "https://external-service.com/validator", 
            summary, 
            context
        )
        
        return f"Workflow complete: {validation}"
```

### Using `call_other_agent()`

For more explicit context propagation and A2A-specific features:

```python
class ProcessingAgent(Agent):
    async def process_message(self, message: str, context: AgentContext) -> str:
        # Call another agent with full A2A response
        response = await self.call_other_agent(
            "/data-processor", 
            message, 
            context, 
            timeout=60.0
        )
        
        # Access different response formats
        text_result = response.text  # Simple text access
        structured_data = response.data  # Structured data access
        all_parts = response.parts  # All response parts
        raw_response = response.raw_response  # Full A2A response
        
        return f"Processed: {text_result}"
```

## Agent Registry Configuration

For agent name resolution, configure a registry using environment variables:

### Option 1: Environment Variable

```bash
export AGENT_REGISTRY='{"document-analyzer": "http://analyzer:8000", "summarizer": "http://summarizer:8001"}'
```

### Option 2: Configuration File

Create `agents.json`:

```json
{
  "agents": {
    "document-analyzer": {
      "url": "http://localhost:8010",
      "name": "Document Analyzer",
      "description": "Analyzes document structure and content"
    },
    "summarizer": {
      "url": "http://localhost:8002", 
      "name": "Text Summarizer"
    },
    "protocol-generator": {
      "url": "http://localhost:8003"
    }
  }
}
```

Set the registry path:

```bash
export AGENT_REGISTRY_PATH="./agents.json"
```

## Multi-Agent Deployment

Deploy multiple agents in a single server for efficient local communication:

```python
from health_universe_a2a import serve_multi_agents

# Define your agents
orchestrator = OrchestratorAgent()
analyzer = DocumentAnalyzerAgent()
summarizer = SummarizerAgent()

# Serve all agents on one server
if __name__ == "__main__":
    serve_multi_agents({
        "/orchestrator": orchestrator,
        "/analyzer": analyzer,
        "/summarizer": summarizer,
    }, port=8501)
```

Now agents can call each other using relative paths:

```python
class OrchestratorAgent(Agent):
    async def process_message(self, message: str, context: AgentContext) -> str:
        # Call local agents efficiently
        analysis = await self.call_agent("/analyzer", message, context)
        summary = await self.call_agent("/summarizer", analysis, context)
        return summary
```

## Advanced Communication Patterns

### Sequential Processing

Chain multiple agents for step-by-step processing:

```python
async def process_message(self, message: str, context: AgentContext) -> str:
    await context.update_progress("Starting document processing...", 0.1)
    
    # Step 1: Extract text
    extracted = await self.call_agent("text-extractor", message, context)
    await context.update_progress("Text extracted", 0.3)
    
    # Step 2: Analyze content
    analysis = await self.call_agent("content-analyzer", extracted, context)
    await context.update_progress("Content analyzed", 0.6)
    
    # Step 3: Generate summary
    summary = await self.call_agent("summarizer", analysis, context)
    await context.update_progress("Summary generated", 0.9)
    
    return summary
```

### Parallel Processing

Call multiple agents concurrently for improved performance:

```python
import asyncio

async def process_message(self, message: str, context: AgentContext) -> str:
    # Launch multiple agents in parallel
    tasks = [
        self.call_agent("sentiment-analyzer", message, context),
        self.call_agent("keyword-extractor", message, context),
        self.call_agent("entity-recognizer", message, context)
    ]
    
    # Wait for all to complete
    sentiment, keywords, entities = await asyncio.gather(*tasks)
    
    # Combine results
    combined_analysis = {
        "sentiment": sentiment,
        "keywords": keywords,
        "entities": entities
    }
    
    return json.dumps(combined_analysis)
```

### Error Handling and Fallbacks

Implement robust error handling for agent communication:

```python
async def process_message(self, message: str, context: AgentContext) -> str:
    try:
        # Try primary processing agent
        result = await self.call_agent(
            "primary-processor", 
            message, 
            context,
            timeout=30.0
        )
        return result
    
    except Exception as e:
        logger.warning(f"Primary processor failed: {e}")
        
        try:
            # Fallback to secondary agent
            result = await self.call_agent(
                "backup-processor", 
                message, 
                context,
                timeout=60.0
            )
            return f"Processed via backup: {result}"
        
        except Exception as fallback_error:
            logger.error(f"Backup processor also failed: {fallback_error}")
            return "Processing failed - manual review required"
```

### Structured Data Communication

Pass complex data structures between agents:

```python
class DataProcessorAgent(Agent):
    async def process_message(self, message: str, context: AgentContext) -> str:
        # Parse input
        request_data = json.loads(message)
        
        # Prepare structured data for next agent
        analysis_request = {
            "document_ids": request_data.get("documents", []),
            "analysis_type": "comprehensive",
            "parameters": {
                "include_sentiment": True,
                "extract_entities": True,
                "generate_summary": True
            }
        }
        
        # Call with structured data
        response = await self.call_other_agent_with_data(
            "/document-analyzer",
            analysis_request,
            context
        )
        
        # Parse structured response
        analysis_results = response.data
        return json.dumps(analysis_results)
```

## Best Practices

### 1. Design for Reliability

```python
async def reliable_agent_call(self, agent_id: str, message: str, context: AgentContext):
    max_retries = 3
    for attempt in range(max_retries):
        try:
            return await self.call_agent(agent_id, message, context, timeout=30.0)
        except Exception as e:
            if attempt == max_retries - 1:
                raise
            await asyncio.sleep(2 ** attempt)  # Exponential backoff
```

### 2. Use Appropriate Timeouts

```python
# Quick operations
result = await self.call_agent("validator", data, context, timeout=10.0)

# Heavy processing
analysis = await self.call_agent("ml-analyzer", data, context, timeout=300.0)
```

### 3. Propagate Context Information

```python
async def process_message(self, message: str, context: AgentContext) -> str:
    # Context is automatically propagated with JWT tokens
    # User ID, thread ID, and file access tokens are maintained
    result = await self.call_agent("processor", message, context)
    
    # The called agent will have access to the same documents and user context
    return result
```

### 4. Monitor and Log Communication

```python
async def process_message(self, message: str, context: AgentContext) -> str:
    start_time = time.time()
    
    try:
        await context.update_progress("Calling analysis agent...", 0.2)
        result = await self.call_agent("analyzer", message, context)
        
        duration = time.time() - start_time
        self.logger.info(f"Agent call completed in {duration:.2f}s")
        
        return result
    
    except Exception as e:
        self.logger.error(f"Agent call failed after {time.time() - start_time:.2f}s: {e}")
        raise
```

## Example: Complete Document Processing Workflow

Here's a comprehensive example showing a document processing workflow with multiple specialized agents:

```python
from health_universe_a2a import Agent, AgentContext
import json
import asyncio

class DocumentWorkflowOrchestrator(Agent):
    def get_agent_name(self) -> str:
        return "Document Workflow Orchestrator"

    def get_agent_description(self) -> str:
        return "Orchestrates complete document processing workflows"

    async def process_message(self, message: str, context: AgentContext) -> str:
        workflow_config = json.loads(message)
        document_ids = workflow_config.get("document_ids", [])
        
        await context.update_progress("Starting document workflow", 0.1)
        
        results = []
        
        for i, doc_id in enumerate(document_ids):
            try:
                # Step 1: Extract and preprocess
                await context.update_progress(f"Processing document {i+1}/{len(document_ids)}: Extracting", 
                                            0.1 + (i * 0.8 / len(document_ids)))
                
                extraction = await self.call_agent(
                    "text-extractor", 
                    json.dumps({"document_id": doc_id}), 
                    context
                )
                
                # Step 2: Analyze content in parallel
                analysis_tasks = [
                    self.call_agent("medical-ner", extraction, context),
                    self.call_agent("sentiment-analyzer", extraction, context),
                    self.call_agent("clinical-classifier", extraction, context)
                ]
                
                entities, sentiment, classification = await asyncio.gather(*analysis_tasks)
                
                # Step 3: Generate structured report
                report_data = {
                    "document_id": doc_id,
                    "entities": json.loads(entities),
                    "sentiment": json.loads(sentiment),
                    "classification": json.loads(classification)
                }
                
                report = await self.call_agent(
                    "report-generator",
                    json.dumps(report_data),
                    context
                )
                
                results.append({
                    "document_id": doc_id,
                    "status": "completed",
                    "report": json.loads(report)
                })
                
            except Exception as e:
                self.logger.error(f"Failed to process document {doc_id}: {e}")
                results.append({
                    "document_id": doc_id,
                    "status": "failed",
                    "error": str(e)
                })
        
        await context.update_progress("Workflow completed", 1.0)
        
        # Save final results
        final_report = {
            "workflow_id": workflow_config.get("workflow_id"),
            "total_documents": len(document_ids),
            "successful": len([r for r in results if r["status"] == "completed"]),
            "failed": len([r for r in results if r["status"] == "failed"]),
            "results": results
        }
        
        await context.document_client.write(
            "Workflow Results",
            json.dumps(final_report, indent=2),
            filename="workflow_results.json"
        )
        
        return f"Processed {len(document_ids)} documents. See workflow_results.json for details."
```

This example demonstrates:

* Sequential and parallel agent calls
* Progress tracking throughout the workflow
* Error handling for individual documents
* Structured data passing between agents
* Result aggregation and persistence

Inter-agent communication enables you to build sophisticated, modular healthcare workflows that leverage the strengths of specialized agents while maintaining clean separation of concerns.


# Document Operations

The Health Universe A2A SDK provides a powerful document client through the `context.document_client` property, allowing your agents to interact with documents in the current thread. This guide covers all the essential operations for working with documents.

## Overview

The document client provides access to documents stored in the Health Universe platform, with support for:

* **Listing** documents in the current thread
* **Reading** document content (both raw and extracted text)
* **Writing** new documents
* **Updating** existing documents
* **Searching** across documents (text and semantic search)
* **Processing status** tracking for extraction pipelines

## Basic Document Operations

### Listing Documents

The most common operation is listing documents available in the current thread:

```python
from health_universe_a2a import Agent, AgentContext

class DocumentProcessor(Agent):
    def get_agent_name(self) -> str:
        return "Document Processor"
    
    def get_agent_description(self) -> str:
        return "Processes and analyzes documents"
    
    async def process_message(self, message: str, context: AgentContext) -> str:
        # List all documents
        docs = await context.document_client.list_documents()
        
        # List only source documents (user uploads)
        source_docs = await context.document_client.list_documents(role="source")
        
        # List only artifacts (agent outputs)
        artifacts = await context.document_client.list_documents(role="artifact")
        
        # Include hidden documents
        all_docs = await context.document_client.list_documents(include_hidden=True)
        
        return f"Found {len(docs)} documents ({len(source_docs)} sources, {len(artifacts)} artifacts)"
```

### Reading Document Content

Once you have documents, you can read their content in several ways:

```python
async def process_message(self, message: str, context: AgentContext) -> str:
    docs = await context.document_client.list_documents(role="source")
    
    if not docs:
        return "No source documents found"
    
    doc = docs[0]
    
    # Read raw document as bytes (for binary files like PDFs)
    raw_content = await context.document_client.download(doc.id)
    
    # Read text documents directly
    if doc.filename.endswith(('.txt', '.csv', '.json', '.md')):
        text_content = await context.document_client.download_text(doc.id)
        print(f"Text content: {text_content[:100]}...")
    
    # Read platform-extracted text (for PDFs, DOCX, images)
    try:
        extracted_text = await context.document_client.download_extracted(doc.id)
        print(f"Extracted markdown: {extracted_text[:200]}...")
    except ValueError as e:
        print(f"Extraction not available: {e}")
    
    return f"Processed document: {doc.name}"
```

### Filtering Documents

Use the `filter_by_name()` method for quick document filtering:

```python
async def process_message(self, message: str, context: AgentContext) -> str:
    # Find documents with "protocol" in the name
    protocols = await context.document_client.filter_by_name("protocol")
    
    # Find JSON files
    json_docs = await context.document_client.filter_by_name(".json")
    
    # Find reports
    reports = await context.document_client.filter_by_name("report")
    
    return f"Found {len(protocols)} protocols, {len(json_docs)} JSON files, {len(reports)} reports"
```

## Writing and Updating Documents

### Creating New Documents

The `write()` method handles the complete upload process automatically:

```python
import json

async def process_message(self, message: str, context: AgentContext) -> str:
    # Analyze some data
    analysis_results = {
        "summary": "Analysis complete",
        "score": 0.95,
        "recommendations": ["Action 1", "Action 2"]
    }
    
    # Write JSON results
    await context.document_client.write(
        "Analysis Results",
        json.dumps(analysis_results, indent=2),
        filename="analysis.json"
    )
    
    # Write markdown report
    markdown_report = """# Analysis Report
    
## Summary
The analysis has been completed successfully.

## Key Findings
- Score: 0.95
- High confidence in results
- Two recommendations identified

## Recommendations
1. Action 1
2. Action 2
"""
    
    await context.document_client.write(
        "Clinical Analysis Report",
        markdown_report,
        filename="report.md"
    )
    
    # Write binary data (e.g., generated PDF)
    # pdf_bytes = generate_pdf_report(analysis_results)
    # await context.document_client.write(
    #     "PDF Report",
    #     pdf_bytes,
    #     filename="report.pdf"
    # )
    
    return "Analysis complete! Results saved as JSON and markdown."
```

### Updating Existing Documents

Update existing documents to create new versions:

```python
async def process_message(self, message: str, context: AgentContext) -> str:
    # Find existing results document
    results_docs = await context.document_client.filter_by_name("results")
    
    if results_docs:
        doc = results_docs[0]
        
        # Update with new data
        updated_results = {
            "timestamp": "2024-01-15T10:30:00Z",
            "version": 2,
            "data": "Updated analysis results"
        }
        
        await context.document_client.update(
            doc.id,
            json.dumps(updated_results, indent=2),
            comment="Updated with latest analysis"
        )
        
        return f"Updated {doc.name} to version 2"
    
    return "No results document found to update"
```

## Document Search

### Text Search

Perform keyword-based searches across document content:

```python
async def process_message(self, message: str, context: AgentContext) -> str:
    # Search for specific terms
    results = await context.document_client.search("medication dosage", limit=5)
    
    findings = []
    for result in results:
        findings.append(f"Found in {result.document_name} (chunk {result.chunk_index}): {result.content[:100]}...")
    
    return f"Found {len(results)} matches:\n" + "\n".join(findings)
```

### Semantic Search

Use AI-powered semantic search to find conceptually related content:

```python
async def process_message(self, message: str, context: AgentContext) -> str:
    # Semantic search finds related concepts, not just exact keywords
    results = await context.document_client.semantic_search(
        "What are the contraindications for this treatment?",
        max_results=3,
        similarity_threshold=0.6
    )
    
    findings = []
    for result in results:
        findings.append(
            f"[{result.similarity:.2f}] {result.document_name}: {result.content[:150]}..."
        )
    
    return f"Found {len(results)} relevant sections:\n" + "\n".join(findings)
```

## Processing Status and Waiting

### Checking Processing Status

Monitor document extraction status:

```python
async def process_message(self, message: str, context: AgentContext) -> str:
    docs = await context.document_client.list_documents(role="source")
    
    status_summary = []
    for doc in docs:
        status = await context.document_client.get_processing_status(doc.id)
        status_summary.append(
            f"{doc.name}: {status.status} "
            f"({'ready' if status.is_ready else 'processing'})"
        )
    
    return "Document status:\n" + "\n".join(status_summary)
```

### Waiting for Processing

Wait for documents to be fully processed before proceeding:

```python
async def process_message(self, message: str, context: AgentContext) -> str:
    await context.update_progress("Waiting for documents to be processed...", 0.1)
    
    try:
        # Wait up to 5 minutes for all documents to be ready
        statuses = await context.document_client.wait_for_ready(timeout=300.0)
        
        ready_count = sum(1 for s in statuses if s.is_ready)
        total_count = len(statuses)
        
        await context.update_progress(f"Documents ready: {ready_count}/{total_count}", 0.5)
        
        # Process documents that are ready
        for status in statuses:
            if status.is_ready:
                extracted = await context.document_client.download_extracted(status.document_id)
                # Process the extracted text...
        
        return f"Successfully processed {ready_count} documents"
        
    except TimeoutError:
        return "Some documents are still processing. Please try again later."
```

## Run Storage

For agents invoked via routines, the SDK provides access to run storage for ephemeral I/O operations:

```python
async def process_message(self, message: str, context: AgentContext) -> str:
    storage = context.run_storage
    
    if storage and storage.has_inputs:
        # Download all input files from the routine
        input_files = storage.input_files
        for file in input_files:
            content = await storage.download_input(file.filename)
            # Process the input content...
        
        # Upload outputs for the next routine step
        results = {"status": "processed", "count": len(input_files)}
        await storage.upload_output(
            "results.json",
            json.dumps(results),
            "application/json",
            "Processing results"
        )
        
        # Finalize to create the manifest
        await storage.finalize()
        
        return f"Processed {len(input_files)} routine inputs"
    
    return "No routine storage available"
```

## Complete Example: Document Analysis Agent

Here's a comprehensive example that combines multiple document operations:

```python
from health_universe_a2a import Agent, AgentContext
import json

class DocumentAnalyzer(Agent):
    def get_agent_name(self) -> str:
        return "Document Analyzer"
    
    def get_agent_description(self) -> str:
        return "Analyzes uploaded documents and generates insights"
    
    async def process_message(self, message: str, context: AgentContext) -> str:
        await context.update_progress("Starting document analysis...", 0.1)
        
        # List source documents
        docs = await context.document_client.list_documents(role="source")
        if not docs:
            return "No documents found to analyze"
        
        await context.update_progress(f"Found {len(docs)} documents to analyze", 0.2)
        
        analysis_results = {
            "documents_analyzed": len(docs),
            "findings": [],
            "summary": ""
        }
        
        # Analyze each document
        for i, doc in enumerate(docs):
            progress = 0.2 + (0.6 * (i + 1) / len(docs))
            await context.update_progress(f"Analyzing {doc.name}...", progress)
            
            try:
                # Get extracted text
                content = await context.document_client.download_extracted(doc.id)
                
                # Perform analysis (simplified)
                word_count = len(content.split())
                char_count = len(content)
                
                analysis_results["findings"].append({
                    "document": doc.name,
                    "word_count": word_count,
                    "char_count": char_count,
                    "content_preview": content[:200] + "..." if len(content) > 200 else content
                })
                
            except ValueError:
                # Document not ready or extraction failed
                analysis_results["findings"].append({
                    "document": doc.name,
                    "status": "extraction_not_available"
                })
        
        # Generate summary
        total_words = sum(f.get("word_count", 0) for f in analysis_results["findings"])
        analysis_results["summary"] = f"Analyzed {len(docs)} documents with {total_words} total words"
        
        await context.update_progress("Saving analysis results...", 0.9)
        
        # Save results as a new document
        await context.document_client.write(
            "Document Analysis Results",
            json.dumps(analysis_results, indent=2),
            filename="analysis_results.json"
        )
        
        await context.update_progress("Analysis complete!", 1.0)
        
        return f"Analysis complete! Processed {len(docs)} documents. Results saved to analysis_results.json."

if __name__ == "__main__":
    agent = DocumentAnalyzer()
    agent.serve()
```

## Best Practices

1. **Error Handling**: Always handle cases where documents might not be ready or extraction fails:

   ```python
   try:
       content = await context.document_client.download_extracted(doc.id)
   except ValueError as e:
       print(f"Could not extract text: {e}")
       # Fall back to raw content or skip
   ```
2. **Progress Updates**: Keep users informed during long document processing:

   ```python
   for i, doc in enumerate(docs):
       await context.update_progress(f"Processing {doc.name}...", i / len(docs))
   ```
3. **Resource Management**: The document client is automatically managed, but you can close it explicitly if needed:

   ```python
   # Usually not needed - handled automatically
   await context.document_client.close()
   ```
4. **Check Document Types**: Verify document types before processing:

   ```python
   if doc.filename.endswith('.pdf'):
       # Use extracted text for PDFs
       content = await context.document_client.download_extracted(doc.id)
   elif doc.filename.endswith('.csv'):
       # Read CSV directly
       content = await context.document_client.download_text(doc.id)
   ```
5. **Use Run Storage for Routines**: When building agents for routine workflows, leverage run storage for efficient I/O:

   ```python
   if context.run_storage and context.run_storage.has_inputs:
       # Process routine inputs directly from S3
       paths = await context.run_storage.download_all_inputs(work_dir)
   ```

The document client provides a comprehensive interface for working with files in Health Universe. Whether you're building document processors, analysis tools, or data extraction agents, these operations give you the flexibility to handle various document workflows efficiently.


# Local Development

When developing A2A agents, it's essential to test your agent locally before deploying to Health Universe. The SDK provides local development tools that simulate the production environment without requiring backend services.

This guide shows you how to set up and test your agents locally using the `LocalDocumentClient` and `create_local_context`.

## Overview

Local development with A2A agents allows you to:

* Test agent logic without deploying to Health Universe
* Work with local test files instead of cloud storage
* Debug your agent's document processing capabilities
* Validate input/output behavior in isolation

The SDK provides two key utilities for local testing:

* **`LocalDocumentClient`**: Filesystem-backed document operations
* **`create_local_context`**: Factory for creating test contexts

## Setting Up Local Test Data

### Directory Structure

Create a local test data directory with the following structure:

```
test_data/
├── source/          # Input documents (user uploads)
│   ├── protocol.pdf
│   ├── data.csv
│   └── notes.txt
└── artifact/        # Pre-seeded outputs (optional)
    └── example.json
```

The SDK will automatically create these directories if they don't exist.

### Supported File Types

The `LocalDocumentClient` works with any file type, but for realistic testing, use files similar to what users will upload:

* **PDFs**: Clinical protocols, research papers
* **CSV**: Patient data, lab results
* **JSON**: Structured medical data
* **TXT/MD**: Clinical notes, documentation

## Creating a Local Context

The simplest way to create a local test environment is using `create_local_context()`:

```python
from health_universe_a2a.local import create_local_context

# Create context with default output directory
context = create_local_context(data_dir="./test_data")

# Or specify a custom output directory
context = create_local_context(
    data_dir="./test_data",
    output_dir="./output"
)
```

## Testing Your Agent Locally

### Basic Local Testing

Here's a complete example of testing an agent locally:

```python
import asyncio
import json
from health_universe_a2a import Agent, AgentContext
from health_universe_a2a.local import create_local_context

class DocumentAnalyzer(Agent):
    def get_agent_name(self) -> str:
        return "Document Analyzer"

    def get_agent_description(self) -> str:
        return "Analyzes uploaded documents and generates insights"

    async def process_message(self, message: str, context: AgentContext) -> str:
        # List all source documents
        docs = await context.document_client.list_documents(role="source")
        print(f"Found {len(docs)} source documents")

        results = []
        for doc in docs:
            # Read document content (works for text files)
            try:
                content = await context.document_client.download_text(doc.id)
                word_count = len(content.split())
                results.append({
                    "document": doc.name,
                    "word_count": word_count,
                    "size_bytes": len(content.encode())
                })
                
                # Update progress
                await context.update_progress(
                    f"Analyzed {doc.name}",
                    progress=len(results) / len(docs)
                )
            except UnicodeDecodeError:
                # Handle binary files
                content_bytes = await context.document_client.download(doc.id)
                results.append({
                    "document": doc.name,
                    "type": "binary",
                    "size_bytes": len(content_bytes)
                })

        # Write results to output
        await context.document_client.write(
            "Analysis Results",
            json.dumps(results, indent=2),
            filename="analysis.json"
        )

        return f"Analyzed {len(docs)} documents. Results saved to analysis.json"

async def test_locally():
    # Create agent and local context
    agent = DocumentAnalyzer()
    context = create_local_context("./test_data")

    # Test the agent
    result = await agent.process_message("Analyze documents", context)
    print(f"Result: {result}")

if __name__ == "__main__":
    asyncio.run(test_locally())
```

### Advanced Local Testing

For more sophisticated testing, you can create the `LocalDocumentClient` directly:

```python
from health_universe_a2a.local import LocalDocumentClient
from health_universe_a2a import BackgroundContext

async def advanced_local_test():
    # Create document client
    doc_client = LocalDocumentClient(
        data_dir="./test_data",
        output_dir="./output"
    )

    # Build context with local document client
    context = BackgroundContext(
        user_id="test-user",
        thread_id="test-thread",
        job_id="test-job-123"
    )
    # Override with local document client
    context._document_client_override = doc_client

    # Test document operations
    docs = await doc_client.list_documents()
    print(f"Available documents: {[d.name for d in docs]}")

    # Filter by name
    protocols = await doc_client.filter_by_name("protocol")
    print(f"Protocol documents: {[d.name for d in protocols]}")

if __name__ == "__main__":
    asyncio.run(advanced_local_test())
```

## Working with Different File Types

### Text Files

For text-based files (CSV, JSON, TXT, MD), use `download_text()`:

```python
# Read CSV data
csv_docs = await context.document_client.filter_by_name("data.csv")
if csv_docs:
    csv_content = await context.document_client.download_text(csv_docs[0].id)
    lines = csv_content.split('\n')
    print(f"CSV has {len(lines)} rows")
```

### Binary Files

For binary files (PDF, images, etc.), use `download()`:

```python
# Read PDF bytes
pdf_docs = await context.document_client.filter_by_name("protocol.pdf")
if pdf_docs:
    pdf_bytes = await context.document_client.download(pdf_docs[0].id)
    print(f"PDF size: {len(pdf_bytes)} bytes")
    
    # Save to local file for inspection
    with open("downloaded_protocol.pdf", "wb") as f:
        f.write(pdf_bytes)
```

### Creating Test Output

Write various output formats to test different scenarios:

```python
# JSON output
await context.document_client.write(
    "Structured Results",
    json.dumps({"score": 0.95, "status": "complete"}),
    filename="results.json"
)

# Markdown report
report = """
# Analysis Report

## Summary
Document analysis completed successfully.

## Findings
- 3 documents processed
- Average word count: 1,250
- No errors detected
"""

await context.document_client.write(
    "Analysis Report",
    report,
    filename="report.md"
)

# CSV output
csv_data = "document,words,score\nprotocol.pdf,1500,0.95\ndata.csv,50,0.80"
await context.document_client.write(
    "Document Scores",
    csv_data,
    filename="scores.csv"
)
```

## Limitations of Local Mode

When testing locally, be aware of these limitations:

### Search Not Available

Full-text and semantic search are not available in local mode:

```python
# This will raise NotImplementedError in local mode
try:
    results = await context.document_client.search("metformin")
except NotImplementedError:
    print("Search not available in local mode")

# This will also raise NotImplementedError
try:
    results = await context.document_client.semantic_search("drug interactions")
except NotImplementedError:
    print("Semantic search not available in local mode")
```

### No Text Extraction Pipeline

Local mode doesn't have the platform's text extraction pipeline:

```python
# In production, this extracts text from PDFs
# In local mode, it falls back to download_text() (will fail for PDFs)
try:
    extracted = await context.document_client.download_extracted(pdf_doc.id)
except UnicodeDecodeError:
    print("Text extraction not available locally for binary files")
```

### Progress Updates Are Logged Only

Progress updates are logged to stdout instead of being sent to the backend:

```python
# This will print to console in local mode
await context.update_progress("Processing...", 0.5)
# Output: [LOCAL] Processing... (50.0%)
```

## Best Practices

### Organize Test Data

Keep your test data organized and representative:

```python
# Create different test scenarios
test_scenarios = [
    "test_data/clinical_trial",      # Clinical trial documents
    "test_data/patient_records",     # Patient data files  
    "test_data/lab_results",         # Laboratory data
    "test_data/edge_cases"           # Error/edge case files
]

for scenario in test_scenarios:
    print(f"\nTesting scenario: {scenario}")
    context = create_local_context(scenario)
    result = await agent.process_message("test", context)
    print(f"Result: {result}")
```

### Test Error Conditions

Include problematic files to test error handling:

```python
async def test_error_handling():
    # Test with empty directory
    context = create_local_context("./empty_data")
    result = await agent.process_message("test empty", context)
    
    # Test with corrupted files
    context = create_local_context("./corrupted_data") 
    result = await agent.process_message("test corrupted", context)
```

### Validate Output Structure

Check that your agent produces the expected output format:

```python
# After running agent locally
output_docs = await context.document_client.list_documents(role="artifact")
assert len(output_docs) > 0, "Agent should produce output documents"

# Verify specific output file
results_docs = await context.document_client.filter_by_name("results.json")
assert len(results_docs) == 1, "Should produce exactly one results file"

results_content = await context.document_client.download_text(results_docs[0].id)
results_data = json.loads(results_content)
assert "score" in results_data, "Results should include score field"
```

## Moving from Local to Production

Once your agent works locally, deploying to production should be seamless:

1. **Keep the same agent code** - no changes needed
2. **Replace local context** with production `AgentContext`
3. **Remove local test code** - the production environment handles context creation
4. **Test with real data** - upload test files to your Health Universe thread

The `context.document_client` API works identically in both local and production modes, so your document processing code remains the same.

## Example: Complete Local Development Workflow

Here's a complete example showing the full local development workflow:

```python
#!/usr/bin/env python3
"""
Local development script for Document Analyzer agent
"""

import asyncio
import json
import logging
from pathlib import Path
from health_universe_a2a import Agent, AgentContext
from health_universe_a2a.local import create_local_context

# Set up logging
logging.basicConfig(level=logging.INFO)

class DocumentAnalyzer(Agent):
    def get_agent_name(self) -> str:
        return "Document Analyzer"

    def get_agent_description(self) -> str:
        return "Analyzes documents and generates insights"

    async def process_message(self, message: str, context: AgentContext) -> str:
        # Your agent logic here
        docs = await context.document_client.list_documents(role="source")
        
        if not docs:
            return "No documents found to analyze"
        
        # Process documents
        results = []
        for i, doc in enumerate(docs):
            await context.update_progress(
                f"Analyzing {doc.name}...", 
                progress=(i + 1) / len(docs)
            )
            
            # Analyze document (simplified example)
            try:
                content = await context.document_client.download_text(doc.id)
                results.append({
                    "name": doc.name,
                    "word_count": len(content.split()),
                    "char_count": len(content)
                })
            except UnicodeDecodeError:
                # Binary file
                content = await context.document_client.download(doc.id)
                results.append({
                    "name": doc.name,
                    "type": "binary",
                    "size": len(content)
                })
        
        # Save results
        await context.document_client.write(
            "Analysis Results",
            json.dumps(results, indent=2),
            filename="analysis_results.json"
        )
        
        return f"Analyzed {len(docs)} documents successfully"

async def main():
    """Run local development testing"""
    
    # Setup test data
    test_dir = Path("./test_data")
    test_dir.mkdir(exist_ok=True)
    (test_dir / "source").mkdir(exist_ok=True)
    
    # Create sample test file if none exist
    source_dir = test_dir / "source"
    if not any(source_dir.iterdir()):
        (source_dir / "sample.txt").write_text(
            "This is a sample document for testing the Document Analyzer agent."
        )
        print("Created sample test file: test_data/source/sample.txt")
    
    # Initialize agent and context
    agent = DocumentAnalyzer()
    context = create_local_context(str(test_dir))
    
    print(f"Testing {agent.get_agent_name()}")
    print(f"Description: {agent.get_agent_description()}")
    print("-" * 50)
    
    # Test the agent
    result = await agent.process_message("Analyze all documents", context)
    
    print(f"\nAgent Result: {result}")
    
    # Show output files
    output_docs = await context.document_client.list_documents(role="artifact")
    if output_docs:
        print(f"\nGenerated {len(output_docs)} output file(s):")
        for doc in output_docs:
            print(f"  - {doc.name} ({doc.filename})")
    
    print("\nLocal testing complete!")

if __name__ == "__main__":
    asyncio.run(main())
```

Save this as `test_local.py` and run it to test your agent locally before deployment.

This local development approach lets you iterate quickly, debug issues, and ensure your agent works correctly before deploying to Health Universe.


# Observability

The Health Universe A2A SDK includes built-in observability. When your agent runs in production on Health Universe, every execution is automatically traced — including LLM calls, document operations, and inter-agent communication — with zero code changes required.

Traces flow into the Health Universe [Observer dashboard](/overview/observability/observer), giving you and your workspace admins visibility into safety scores, latency, token usage, costs, and user feedback.

## What Gets Traced

The SDK automatically traces the following operations:

### Agent Execution

Every call to `process_message()` creates a top-level trace with:

* **Agent name** and type (AsyncAgent or SubAgent)
* **Input** (user message) and **output** (agent response)
* **Duration** of the entire execution
* **Metadata**: job ID, user ID, thread ID, app ID

### LLM Calls

All calls to OpenAI, Anthropic, and other supported LLM providers are captured automatically via OpenTelemetry instrumentation. Each LLM call records:

* Model name
* Token usage (input, output, total)
* Latency
* Request and response content (when content capture is enabled)

Supported LLM providers: **OpenAI**, **Anthropic**, **Google Generative AI**, **Cohere**, **Hugging Face Transformers**

### Document Operations

Every document operation is traced as a child span:

| Operation               | Span Name                     | Metadata                                            |
| ----------------------- | ----------------------------- | --------------------------------------------------- |
| List documents          | `document.list`               | Role filter (source, artifact, all)                 |
| Download document       | `document.download`           | Document ID                                         |
| Download extracted text | `document.download_extracted` | Document ID                                         |
| Write document          | `document.write`              | Document name, filename, content size, storage path |

### Inter-Agent Communication

Calls to other agents via `call_agent()` or `call_other_agent()` are traced as child spans:

| Operation          | Span Name                         | Metadata                                 |
| ------------------ | --------------------------------- | ---------------------------------------- |
| Call another agent | `inter_agent.call:{target_agent}` | Target agent identifier, message preview |

## Adding Custom Traces

While the SDK traces all built-in operations automatically, you can add your own traces for custom logic.

### Using the `@observe` Decorator

```python
from health_universe_a2a.observability import observe

class MyAgent(Agent):
    @observe(capture_input=True, capture_output=True)
    async def analyze_data(self, data: dict) -> dict:
        # This method is automatically traced as a child span
        results = await self.run_analysis(data)
        return results

    async def process_message(self, message: str, context: AgentContext) -> str:
        data = await self.load_data(context)
        results = await self.analyze_data(data)
        return json.dumps(results)
```

### Using the Context Manager

For inline spans within a method:

```python
No change needed - import is correct

async def process_message(self, message: str, context: AgentContext) -> str:
    with traced_span("validation", metadata={"input_length": len(message)}) as span:
        validated = self.validate_input(message)
        span.update(output="Validation passed")

    with traced_span("analysis", metadata={"model": "gpt-4o"}) as span:
        result = await self.run_analysis(validated)
        span.update(output=f"Processed {len(result)} items")

    return json.dumps(result)
```

> Both the decorator and context manager are no-ops when observability is disabled — no need for conditional checks in your code.

## Viewing Traces

Once your agent is deployed, traces appear in the Health Universe [Observer dashboard](/overview/observability/observer), which aggregates them into safety scores, usage metrics, cost breakdowns, and more.


# API Reference

Complete reference for the Health Universe A2A SDK for Python.

## Core Classes

### Agent (AsyncAgent)

The primary agent class for Health Universe. Use this for all Health Universe agents.

```python
from health_universe_a2a import Agent, AgentContext  # Agent is alias for AsyncAgent

class MyAgent(Agent):
    def get_agent_name(self) -> str:
        return "My Agent"

    def get_agent_description(self) -> str:
        return "Processes medical data"

    async def process_message(self, message: str, context: AgentContext) -> str:
        return f"Processed: {message}"
```

#### Required Methods

* **`get_agent_name() -> str`**
  * Returns the agent's display name
  * Example: `"Clinical Data Analyzer"`
* **`get_agent_description() -> str`**
  * Returns what the agent does
  * Example: `"Analyzes clinical datasets and generates insights"`
* **`async process_message(message: str, context: AgentContext) -> str`**
  * Processes messages and returns results
  * Called in background after validation passes
  * All updates sent via POST to backend

#### Optional Methods

* **`async validate_message(message: str, metadata: dict[str, Any]) -> ValidationResult`**
  * Validates incoming messages before processing
  * Returns `ValidationAccepted` or `ValidationRejected`
  * Default: accepts all messages
* **`get_max_duration_seconds() -> int`**
  * Override max task duration (default: 1 hour)
  * Returns maximum duration in seconds
* **`async on_task_start(message: str, context: AgentContext) -> None`**
  * Called before `process_message` starts in background
  * Use for logging, metrics, setup
* **`async on_task_complete(message: str, result: str, context: AgentContext) -> None`**
  * Called after `process_message` completes successfully
  * Use for logging, metrics, cleanup
* **`async on_task_error(message: str, error: Exception, context: AgentContext) -> str | None`**
  * Called when `process_message` raises an exception
  * Return custom error message or `None` for default

#### Server Methods

* **`serve(host: str | None = None, port: int | None = None, reload: bool | None = None, log_level: str = "info") -> None`**
  * Start HTTP server for this agent
  * Environment variables: `HOST`, `PORT`/`AGENT_PORT`, `RELOAD`

#### Inter-Agent Communication

* **`async call_agent(agent_name_or_url: str, message: str | dict | list | Any, context: BaseContext | None = None, timeout: float = 30.0) -> Any`**
  * Unified method to call another A2A agent
  * Supports agent names, URLs, or local paths
  * Automatically propagates JWT tokens

### AgentContext (BackgroundContext)

Context for Agent execution. Provides document operations, progress updates, and inter-agent communication.

```python
async def process_message(self, message: str, context: AgentContext) -> str:
    # List documents
    docs = await context.document_client.list_documents()
    
    # Update progress
    await context.update_progress("Processing...", 0.5)
    
    # Write results
    await context.document_client.write("Results", json.dumps(data))
    
    return "Complete!"
```

#### Properties

* **`document_client: DocumentClientBase`** - Document operations in this thread
* **`user_id: str | None`** - User ID from request (optional)
* **`thread_id: str | None`** - Thread/conversation ID (optional)
* **`file_access_token: str | None`** - Access token for file operations
* **`auth_token: str | None`** - JWT token from original request
* **`job_id: str | None`** - Background job ID
* **`metadata: dict[str, Any]`** - Raw metadata from A2A request
* **`extensions: list[str] | None`** - List of extension URIs from request message
* **`run_storage: RunStorage | None`** - S3-based run storage for routine execution I/O

#### Methods

* **`async update_progress(message: str, progress: float | None = None, status: str = "working", importance: UpdateImportance = UpdateImportance.NOTICE) -> None`**
  * Send progress update (streamed via SSE or POSTed to backend)
  * Updates stored in database and displayed to users
  * `progress`: 0.0 to 1.0, `importance`: controls UI visibility
* **`async add_artifact(name: str, content: str, data_type: str = "text/plain", description: str = "", metadata: dict[str, Any] | None = None) -> None`**
  * Add artifact (streamed via SSE or POSTed to backend)
  * Artifacts appear in UI and can be downloaded
  * For persistent storage, use `context.document_client.write()` instead
* **`create_inter_agent_client(agent_identifier: str, local_base_url: str | None = None, timeout: float = 30.0, max_retries: int = 3) -> InterAgentClient`**
  * Create InterAgentClient with automatic JWT propagation
  * Recommended way to call other agents
* **`is_cancelled() -> bool`**
  * Check if task was cancelled (currently always returns `False`)
  * Cancellation feature not fully implemented

#### Sync Methods (ThreadPoolExecutor)

* **`update_progress_sync(message: str, progress: float | None = None, status: str = "working", importance: UpdateImportance = UpdateImportance.NOTICE) -> None`**
* **`add_artifact_sync(name: str, content: str, data_type: str = "text/plain", description: str = "", metadata: dict[str, Any] | None = None) -> None`**

### SubAgent

Agent for internal/inter-agent use. Returns results directly (no background job).

```python
from health_universe_a2a import SubAgent, SubAgentContext

class DocumentReader(SubAgent):
    def get_agent_name(self) -> str:
        return "Document Reader"

    def get_agent_description(self) -> str:
        return "Extracts data from documents"

    async def process_message(self, message: str, context: SubAgentContext) -> str:
        data = json.loads(message)
        return json.dumps({"result": "processed"})
```

#### Required Methods

* Same as Agent: `get_agent_name()`, `get_agent_description()`, `process_message()`

#### Key Differences from Agent

* No background jobs or SSE streaming
* Returns results directly in HTTP response
* Uses `SubAgentContext` (lightweight, no progress updates)
* Processing should complete in < 30 seconds

### SubAgentContext

Lightweight context for SubAgent. Provides document operations and inter-agent communication without progress updates.

#### Properties

* **`document_client: DocumentClientBase`** - Document operations in this thread
* **`user_id: str | None`** - User ID from request
* **`thread_id: str | None`** - Thread/conversation ID
* **`file_access_token: str | None`** - Access token for file operations
* **`auth_token: str | None`** - JWT token for inter-agent calls
* **`metadata: dict[str, Any]`** - Raw metadata from A2A request
* **`extensions: list[str] | None`** - List of extension URIs from request message

#### Methods

* **`create_inter_agent_client(agent_identifier: str, local_base_url: str | None = None, timeout: float = 30.0, max_retries: int = 3) -> InterAgentClient`**
* **`is_cancelled() -> bool`**

## Document Operations

### DocumentClient

Client for document operations in a Health Universe thread. Provides listing, reading, writing, and searching documents.

```python
# Accessed via context.document_client
docs = await context.document_client.list_documents()
content = await context.document_client.download_text(doc_id)
await context.document_client.write("Results", json.dumps(data))
```

#### Document Listing

* **`async list_documents(include_hidden: bool = False, role: Literal["source", "artifact", "all"] | None = None, flatten_attachments: bool = True) -> list[Document]`**
  * List documents in thread
  * `role`: Filter by document type (`"source"` for uploads, `"artifact"` for outputs)
  * `flatten_attachments`: Replace wrapper docs with child attachments
* **`async filter_by_name(query: str) -> list[Document]`**
  * Filter documents by name/filename (case-insensitive substring match)
* **`async get_document(document_id: str) -> Document`**
  * Get document metadata by ID

#### Document Reading

* **`async download(document_id: str) -> bytes`**
  * Download document content as bytes
* **`async download_text(document_id: str, encoding: str = "utf-8") -> str`**
  * Download raw document content as text
  * Only use for text-based files (CSV, JSON, TXT, MD)
* **`async download_extracted(document_id: str) -> str`**
  * Download platform-extracted markdown text
  * For PDFs, DOCX converted to markdown by platform

#### Document Writing

* **`async write(name: str, content: str | bytes, filename: str | None = None, document_type: str = "agent_output", user_visible: bool = True, comment: str | None = None) -> Document`**
  * Write content to new document
  * Handles 3-step upload process automatically
  * `filename`: Auto-generated if not provided
* **`async update(document_id: str, content: str | bytes, comment: str | None = None, base_version_id: str | None = None) -> Document`**
  * Update existing document with new content (creates new version)

#### Document Search

* **`async search(query: str, limit: int = 3, patient_id: str | None = None) -> list[SearchResult]`**
  * Full-text search across documents
  * Case-insensitive text search over extracted chunks
  * Use for keyword-based lookups
* **`async semantic_search(query: str, document_id: str | None = None, max_results: int = 5, similarity_threshold: float = 0.4, patient_id: str | None = None) -> list[SemanticSearchResult]`**
  * Semantic search using vector embeddings
  * Finds semantically similar content
  * Powered by pgvector embeddings

#### Processing Status

* **`async get_processing_status(document_id: str) -> DocumentProcessingStatus`**
  * Get extraction/processing status of document
* **`async wait_for_ready(document_ids: list[str] | None = None, poll_interval: float = 2.0, timeout: float = 300.0, patient_id: str | None = None, organization_id: str | None = None) -> list[DocumentProcessingStatus]`**
  * Poll until documents are ready (extraction complete)
  * If `document_ids` is None, waits for all source documents
* **`async close() -> None`**
  * Close HTTP client

### Document Data Models

#### Document

```python
@dataclass
class Document:
    id: str                          # Document UUID
    name: str                        # Display name
    filename: str                    # Storage filename
    document_type: str              # "user_upload" or "agent_output"
    storage_path: str | None        # S3 storage path
    latest_version: int | None      # Version number
    latest_version_id: str | None   # Version UUID
    user_visible: bool              # Visible to users
```

#### SearchResult

```python
@dataclass
class SearchResult:
    document_name: str              # Document name
    chunk_index: int               # Position in document
    content: str                   # Matching text content
    metadata: dict | None          # Optional chunk metadata
```

#### SemanticSearchResult

```python
@dataclass
class SemanticSearchResult:
    chunk_id: str                  # Chunk UUID
    document_id: str              # Document UUID
    document_name: str            # Document name
    content: str                  # Matching text content
    similarity: float             # Cosine similarity (0-1)
    chunk_index: int             # Position in document
    metadata: dict | None        # Optional chunk metadata
```

#### DocumentProcessingStatus

```python
@dataclass
class DocumentProcessingStatus:
    document_id: str                    # Document UUID
    document_name: str                  # Document filename
    status: str                         # Processing state
    chunks_count: int | None           # Number of chunks
    error_message: str | None          # Error details if failed
    enhanced_ocr_status: str | None    # Enhanced OCR status
    
    @property
    def is_ready(self) -> bool:        # Whether extraction is complete
    
    @property 
    def is_terminal(self) -> bool:     # Whether processing finished
```

## Inter-Agent Communication

### InterAgentClient

Client for calling other A2A-compliant agents with JWT propagation and retry logic.

```python
# Via context (recommended)
client = context.create_inter_agent_client("/processor")
response = await client.call("Process this data")

# Direct instantiation
client = InterAgentClient("/processor", auth_token="jwt-token")
response = await client.call_with_data({"query": "test"})
```

#### Constructor

* **`__init__(agent_identifier: str, auth_token: str | None = None, local_base_url: str | None = None, timeout: float = 30.0, max_retries: int = 3)`**
  * `agent_identifier`: Target agent (`/path`, `http://url`, or name)
  * `auth_token`: JWT token to propagate
  * `local_base_url`: Base URL for local agents (default: from env)
  * `timeout`: Request timeout in seconds
  * `max_retries`: Max retry attempts for transient errors

#### Factory Methods

* **`@classmethod from_registry(agent_name: str, auth_token: str | None = None, timeout: float = 30.0, max_retries: int = 3) -> InterAgentClient`**
  * Create client from registry lookup
  * Resolves agent name using global registry

#### Communication Methods

* **`async call(message: str, timeout: float | None = None) -> AgentResponse`**
  * Call agent with text message
  * Returns parsed response
* **`async call_with_data(data: Any, timeout: float | None = None) -> AgentResponse`**
  * Call agent with structured data (dict, list, etc.)
* **`async close() -> None`**
  * Close client (cleanup)

### AgentResponse

Response from inter-agent call with convenient parsed data access.

```python
response = await client.call("Hello")
print(response.text)        # Extract text content
print(response.data)        # Extract structured data
print(response.raw_response) # Full A2A response
```

#### Properties

* **`text: str`** - Concatenated text from all text parts
* **`data: Any`** - First data part from response (structured data)
* **`parts: list[dict]`** - All parts from response
* **`raw_response: dict`** - Full A2A response

#### Methods

* **`__str__() -> str`** - Returns text content
* **`__repr__() -> str`** - Detailed representation

## Observability

The SDK provides built-in observability via Langfuse integration. When properly configured, all agent executions and LLM calls are automatically traced.

### Core Functions

```python
from health_universe_a2a.observability import (
    setup_observability,
    is_enabled,
    get_langfuse,
    flush_traces,
    traced_span,
    observe
)
```

#### Setup and Status

* **`setup_observability() -> bool`**
  * Initialize observability (lazy, idempotent, thread-safe)
  * Safe to call multiple times — only first call does work
  * Returns `True` if observability is active, `False` otherwise
* **`is_enabled() -> bool`**
  * Check if observability is active
  * Fast path: reads module-level bool
* **`get_langfuse() -> Any | None`**
  * Return the Langfuse client instance, or `None` if disabled
* **`flush_traces() -> None`**
  * Best-effort flush of all pending traces
  * Sync — safe to call from executor

### Custom Tracing

#### `@observe` Decorator

```python
from health_universe_a2a.observability import observe

class MyAgent(Agent):
    @observe(capture_input=True, capture_output=True)
    async def analyze_data(self, data: dict) -> dict:
        # This method is automatically traced as a child span
        results = await self.run_analysis(data)
        return results

    @observe(name="custom-analysis")
    async def process_complex_data(self, data: dict) -> dict:
        # Custom span name
        return await self.complex_analysis(data)

    async def process_message(self, message: str, context: AgentContext) -> str:
        data = await self.load_data(context)
        results = await self.analyze_data(data)
        return json.dumps(results)
```

#### Parameters

* **`capture_input: bool = False`** - Capture function input (default: False for performance)
* **`capture_output: bool = False`** - Capture function output (default: False for performance)
* **`name: str = None`** - Optional span name override
* **`**kwargs`** - Additional kwargs forwarded to `langfuse.decorators.observe`

#### `traced_span` Context Manager

```python
from health_universe_a2a.observability import traced_span

async def process_message(self, message: str, context: AgentContext) -> str:
    with traced_span("validation", metadata={"input_length": len(message)}) as span:
        validated = self.validate_input(message)
        span.update(output="Validation passed")

    with traced_span("analysis", as_type="agent", metadata={"model": "gpt-4o"}) as span:
        result = await self.run_analysis(validated)
        span.update(output=f"Processed {len(result)} items")

    return json.dumps(result)
```

#### Parameters

* **`name: str`** - Span name (e.g., "validation", "document.download")
* **`as_type: str = "span"`** - Langfuse observation type ("span", "agent", "generation")
* **`input: Any = None`** - Optional input data to attach to span
* **`metadata: dict[str, Any] = None`** - Optional metadata dict

### Environment Variables

Configure observability using environment variables:

* **`LANGFUSE_ENABLED`** - Master switch ("true"/"false", default: "false")
* **`LANGFUSE_PUBLIC_KEY`** - Langfuse public key (required when enabled)
* **`LANGFUSE_SECRET_KEY`** - Langfuse secret key (required when enabled)
* **`LANGFUSE_BASE_URL`** or **`LANGFUSE_HOST`** - Langfuse host URL (optional)
* **`HU_OBSERVABILITY_CAPTURE_IO`** - Capture prompts/completions ("true"/"false", default: "false")

### Automatic Tracing

When observability is enabled, the SDK automatically traces:

* **Agent execution** - Every `process_message()` call with duration, metadata, input/output
* **LLM calls** - Model name, token usage, latency (via OpenTelemetry instrumentation)
* **Document operations** - List, download, write operations as child spans
* **Inter-agent communication** - Calls to other agents via `call_agent()`

No code changes required — just set the environment variables.

### Example Setup

```python
# Environment setup (via .env file or system environment)
LANGFUSE_ENABLED=true
LANGFUSE_PUBLIC_KEY=pk-...
LANGFUSE_SECRET_KEY=sk-...

# In your agent
from health_universe_a2a import Agent, AgentContext
from health_universe_a2a.observability import observe, traced_span

class DataProcessor(Agent):
    @observe(capture_input=True)
    async def load_model(self) -> Any:
        # Automatically traced
        return load_pretrained_model()

    async def process_message(self, message: str, context: AgentContext) -> str:
        with traced_span("preprocessing", metadata={"message_len": len(message)}):
            processed_input = self.preprocess(message)
        
        # This is automatically traced due to @observe decorator
        model = await self.load_model()
        
        # LLM calls are automatically traced via OpenTelemetry
        result = await openai.chat.completions.create(...)
        
        return result
```

## Validation

### ValidationResult Types

```python
from health_universe_a2a import ValidationAccepted, ValidationRejected

async def validate_message(self, message: str, metadata: dict) -> ValidationResult:
    if len(message) < 10:
        return ValidationRejected(reason="Message too short")
    return ValidationAccepted(estimated_duration_seconds=60)
```

#### ValidationAccepted

```python
class ValidationAccepted:
    status: Literal["accepted"] = "accepted"
    estimated_duration_seconds: int | None = None
```

#### ValidationRejected

```python
class ValidationRejected:
    status: Literal["rejected"] = "rejected"
    reason: str  # Human-readable reason
```

## Extensions and Types

### UpdateImportance

Controls how updates are propagated to Navigator UI.

```python
from health_universe_a2a import UpdateImportance

await context.update_progress(
    "Processing...", 
    importance=UpdateImportance.NOTICE  # Shown in Navigator
)

await context.update_progress(
    "Debug info...", 
    importance=UpdateImportance.INFO    # Not shown in Navigator
)
```

#### Values

* **`ERROR`** - Something went wrong (pushed to Navigator)
* **`NOTICE`** - Standard progress update (default, pushed to Navigator)
* **`INFO`** - Verbose logging (stored but not pushed)
* **`DEBUG`** - Diagnostic information (stored but not pushed)

### NavigatorTaskStatus

Task status values for Navigator UI.

```python
class NavigatorTaskStatus(str, Enum):
    WORKING = "working"
    COMPLETED = "completed" 
    FAILED = "failed"
```

## Local Development

### LocalDocumentClient

Filesystem-backed document client for local testing.

```python
from health_universe_a2a.local import LocalDocumentClient

doc_client = LocalDocumentClient(
    data_dir="./test_data",
    output_dir="./output"
)
docs = await doc_client.list_documents()
```

#### Constructor

* **`__init__(data_dir: str, output_dir: str | None = None)`**
  * `data_dir`: Root directory with `source/` and `artifact/` subdirectories
  * `output_dir`: Output directory (defaults to `data_dir/artifact/`)

#### Methods

* Same interface as `DocumentClient`
* `search()` and `semantic_search()` raise `NotImplementedError`

### create\_local\_context()

Factory function for local testing context.

```python
from health_universe_a2a.local import create_local_context

context = create_local_context("./test_data", "./output")
result = await agent.process_message("test", context)
```

#### Parameters

* **`data_dir: str`** - Root directory for test data
* **`output_dir: str | None = None`** - Output directory (defaults to `data_dir/artifact/`)

#### Returns

* **`AgentContext`** - Configured for local filesystem operations

## Run Storage

### RunStorage

S3-based run storage via presigned URLs for routine agent execution.

```python
async def process_message(self, message: str, context: AgentContext) -> str:
    storage = context.run_storage
    if storage and storage.has_inputs:
        # Download all input files
        paths = await storage.download_all_inputs(work_dir)
        
        # Upload outputs
        await storage.upload_output("result.json", json_bytes, "application/json")
        await storage.finalize()
    
    return "Done"
```

#### Properties

* **`input_files: list[RunStorageFile]`** - All input files available for this step
* **`has_inputs: bool`** - Whether any input files are available

#### Methods

* **`async download_input(filename: str) -> bytes`** - Download a single input file by filename
* **`async download_all_inputs(target_dir: Path) -> list[Path]`** - Download all input files to a directory
* **`async upload_output(filename: str, content: bytes | str, content_type: str = "application/octet-stream", description: str = "") -> None`** - Upload an output file
* **`async finalize() -> None`** - Write the output manifest (call after all uploads)

### RunStorageFile

```python
@dataclass
class RunStorageFile:
    id: str
    name: str
    filename: str
    download_url: str
    content_type: str | None = None
    source: str = "bundle"  # 'bundle' | 'step_output'
```

## Server Utilities

### create\_app()

Create Starlette ASGI application for an A2A agent.

```python
from health_universe_a2a import create_app

app = create_app(agent)
uvicorn.run(app, host="0.0.0.0", port=8000)
```

#### Parameters

* **`agent: A2AAgentBase`** - Agent instance to serve
* **`task_store: Any | None = None`** - Optional task store (defaults to InMemoryTaskStore)

#### Returns

* **`Any`** - Starlette application with A2A endpoints

### serve()

Start HTTP server for an agent (convenience wrapper).

```python
agent.serve()  # Uses environment variables
agent.serve(host="localhost", port=8080, reload=True)
```

#### Parameters

* **`agent: A2AAgentBase`** - Agent to serve
* **`host: str | None = None`** - Server host (default: from `HOST` env var or "0.0.0.0")
* **`port: int | None = None`** - Server port (default: from `PORT`/`AGENT_PORT` env var or 8000)
* **`reload: bool | None = None`** - Auto-reload (default: from `RELOAD` env var or False)
* **`log_level: str = "info"`** - Uvicorn log level

### Multi-Agent Server

Functions for hosting multiple agents at different paths.

#### create\_multi\_agent\_app()

```python
from health_universe_a2a import create_multi_agent_app

app = create_multi_agent_app({
    "/orchestrator": orchestrator_agent,
    "/processor": processor_agent,
})
```

#### serve\_multi\_agents()

```python
from health_universe_a2a import serve_multi_agents

serve_multi_agents({
    "/orchestrator": orchestrator_agent,
    "/processor": processor_agent,
}, port=8501)
```

## Observability

### observe() Decorator

Add custom traces to your agent methods.

```python
from health_universe_a2a import observe

class MyAgent(Agent):
    @observe(capture_input=True, capture_output=True)
    async def analyze_data(self, data: dict) -> dict:
        # This method is automatically traced
        results = await self.run_analysis(data)
        return results
```

#### Parameters

* **`capture_input: bool = False`** - Capture function input
* **`capture_output: bool = False`** - Capture function output
* **`name: str | None = None`** - Optional span name override

### traced\_span() Context Manager

For inline spans within a method:

```python
The import path is correct - no change needed to the import itself

async def process_message(self, message: str, context: AgentContext) -> str:
    with traced_span("validation", metadata={"input_length": len(message)}) as span:
        validated = self.validate_input(message)
        span.update(output="Validation passed")
    
    return "Done"
```

## Environment Variables

### Server Configuration

* **`HOST`** - Server host (default: "0.0.0.0")
* **`PORT`** or **`AGENT_PORT`** - Server port (default: 8000, multi-agent: 8501)
* **`RELOAD`** - Enable auto-reload ("true"/"false", default: "false")

### Service URLs

* **`HU_NESTJS_URL`** - NestJS API base URL (default: "<https://apps.healthuniverse.com/api/v1>")
* **`LOCAL_AGENT_BASE_URL`** - Base URL for local agents (default: "<http://localhost:8501>")

### Agent Registry

* **`AGENT_REGISTRY`** - JSON string mapping agent names to URLs
* **`AGENT_REGISTRY_PATH`** - Path to agent registry JSON file

### Observability

* **`LANGFUSE_ENABLED`** - Enable Langfuse observability ("true"/"false")
* **`LANGFUSE_PUBLIC_KEY`** - Langfuse public key (pk-...)
* **`LANGFUSE_SECRET_KEY`** - Langfuse secret key (sk-...)
* **`LANGFUSE_BASE_URL`** - Langfuse host URL (optional)
* **`HU_OBSERVABILITY_CAPTURE_IO`** - Capture full prompts/completions ("true"/"false")

### Debugging

* **`DEBUG_HTTP_REQUESTS`** - Enable HTTP request logging ("true"/"false")

## Error Handling

### Common Exceptions

* **`ValueError`** - Invalid parameters, missing documents, unresolved agents
* **`httpx.HTTPError`** - Network errors, API failures
* **`TimeoutError`** - Task timeout, waiting for documents
* **`NotImplementedError`** - Local mode limitations (search functions)

### Best Practices

1. **Validation**: Use `validate_message()` to check inputs before processing
2. **Error Handling**: Implement try/except in `process_message()` and `on_task_error()`
3. **Cancellation**: Check `context.is_cancelled()` in long-running loops
4. **Timeouts**: Use appropriate timeouts for inter-agent calls
5. **Resource Cleanup**: Always call `client.close()` and `document_client.close()`

## Examples

### Basic Agent

```python
from health_universe_a2a import Agent, AgentContext
import json

class DataAnalyzer(Agent):
    def get_agent_name(self) -> str:
        return "Clinical Data Analyzer"
    
    def get_agent_description(self) -> str:
        return "Analyzes clinical datasets and generates insights"
    
    async def process_message(self, message: str, context: AgentContext) -> str:
        # List documents
        docs = await context.document_client.list_documents(role="source")
        
        # Process with progress updates
        await context.update_progress("Loading data...", 0.1)
        
        results = []
        for i, doc in enumerate(docs):
            content = await context.document_client.download_text(doc.id)
            result = self.analyze_document(content)
            results.append(result)
            
            await context.update_progress(
                f"Processed {i+1}/{len(docs)} documents",
                (i+1) / len(docs)
            )
        
        # Write results
        await context.document_client.write(
            "Analysis Results",
            json.dumps(results, indent=2),
            filename="analysis.json"
        )
        
        return f"Analysis complete! Processed {len(docs)} documents."
    
    def analyze_document(self, content: str) -> dict:
        return {"length": len(content), "lines": len(content.splitlines())}

if __name__ == "__main__":
    agent = DataAnalyzer()
    agent.serve()
```

### Inter-Agent Communication

```python
from health_universe_a2a import Agent, AgentContext

class OrchestratorAgent(Agent):
    def get_agent_name(self) -> str:
        return "Document Orchestrator"
    
    def get_agent_description(self) -> str:
        return "Orchestrates document processing workflow"
    
    async def process_message(self, message: str, context: AgentContext) -> str:
        # Call document reader
        reader_response = await self.call_agent(
            "document-reader", 
            message, 
            context
        )
        
        # Call analyzer with reader results
        analyzer_response = await self.call_agent(
            "data-analyzer",
            reader_response,
            context
        )
        
        return f"Workflow complete: {analyzer_response}"
```

### Local Development

```python
from health_universe_a2a.local import create_local_context

async def test_agent_locally():
    agent = DataAnalyzer()
    context = create_local_context("./test_data")
    
    result = await agent.process_message("Analyze documents", context)
    print(f"Result: {result}")

if __name__ == "__main__":
    import asyncio
    asyncio.run(test_agent_locally())
```

### Observability Integration

```python
from health_universe_a2a import Agent, AgentContext
from health_universe_a2a.observability import observe, traced_span
import openai

class AnalysisAgent(Agent):
    @observe(capture_input=True, capture_output=True)
    async def extract_insights(self, content: str) -> dict:
        # This method is automatically traced
        return {"word_count": len(content.split())}
    
    async def process_message(self, message: str, context: AgentContext) -> str:
        with traced_span("document_loading") as span:
            docs = await context.document_client.list_documents(role="source")
            span.update(output=f"Loaded {len(docs)} documents")
        
        # Custom analysis with tracing
        insights = await self.extract_insights(message)
        
        # LLM call - automatically traced via OpenTelemetry
        response = await openai.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": f"Analyze: {insights}"}]
        )
        
        return response.choices[0].message.content

if __name__ == "__main__":
    agent = AnalysisAgent()
    agent.serve()
```


# Deploying your app to Health Universe

Health Universe is intended to make deploying Streamlit and FastAPI apps easy! There are some tricks to manage with regard to connecting to databases, LLMs or other resources that require authentication — the API keys and credentials are stored in the "secrets management" functions within Health Unverse, and they are stored securely so that only you can access that information. This is particularly useful when you are connecting to an LLM or to a secure database.


# Deploying to Health Universe

Deploying your app on Health Universe is a straightforward process that links your GitHub repository to a selected workspace. Follow the steps below to get your app live.

> 📌 **Note:** Make sure your GitHub account is linked to Health Universe before deploying. [Learn how »](https://docs.healthuniverse.com/overview/building-apps-in-health-universe/getting-started-with-health-universe/link-your-github-repository-to-your-health-universe-account)

#### ✅ Step-by-Step Deployment Instructions

**1. Go to Your Workspace**

* Log in to [Health Universe](https://www.healthuniverse.com/).
* Navigate to the [**workspace**](/overview/building-apps-in-health-universe/creating-a-workspace) where you want to deploy your app (e.g., Personal, Organization, Enterprise, etc).
* Double-check that you're in the correct workspace, as deployments are workspace-specific.

**2. Open the Deployment Page**

* In your workspace, click the box labeled: **“Deploy a new app”** or head directly to <https://www.healthuniverse.com/create>.

  <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FA6QLOxV3dzDmZST0SILe%2FScreenshot%202025-05-28%20at%2010.06.10%E2%80%AFAM.png?alt=media&amp;token=e3a348c9-73bf-4340-885b-0cca6d841bce" alt=""><figcaption></figcaption></figure>

**3. Select Your GitHub Repository**

* Under **Step 1: Select GitHub Repository**:
  * If your [GitHub account is properly connected](/overview/building-apps-in-health-universe/getting-started-with-health-universe/link-your-github-account-to-your-health-universe-account), your repositories will appear in a dropdown. Choose the appropriate repository.

    <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FnWm7dpcduSZm4D100aX0%2FScreenshot%202025-05-28%20at%2010.07.45%E2%80%AFAM.png?alt=media&amp;token=dacb5511-5ea2-4e37-9398-95d9f0e1a531" alt=""><figcaption></figcaption></figure>

**4. About Your App**

* Under **Step 2: About**, fill in:
  * **App Workspace:** From the dropdown, select the workspace you want to deploy your app to.
  * **App Name**: Give your app a clear, meaningful name.
  * **Description**: Write a short summary of what the app does.
  * **Research Area**: Choose the relevant field for your app’s purpose.

<figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2F0XqBJvMa30JWT4p2pemR%2FScreenshot%202025-05-28%20at%2010.11.59%E2%80%AFAM.png?alt=media&amp;token=ad8160db-60fb-497e-825e-8e380258ec88" alt=""><figcaption></figcaption></figure>

**5. Choose Runtime**

* Under **Step 3: Runtime**, select your app runtime:

  * **FastAPI**
    * [ ] Standalone
    * [ ] Add to Navigator *(if compatible)*
  * **Streamlit**
    * Always runs in Standalone mode
  * **React / Next.js / Node**
    * Runs in Standalone mode. The runtime is **auto-detected from your `package.json`** — React apps are built and served as a static SPA, while Next.js and Node apps run as a server via `npm start`. See [Working in React](/overview/building-apps-in-health-universe/developing-your-health-universe-app/working-in-react).

  > Only compatible FastAPI apps can be added to Navigator.
* Enter the name of your app’s main file. For Python apps this is your script (e.g., `main.py`); for React/Next.js/Node apps it is `package.json`.

  <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FeOavGOtmBVfjTOctEooz%2FScreenshot%202025-05-28%20at%2010.16.26%E2%80%AFAM.png?alt=media&amp;token=d75cf08d-125b-4e54-9f47-2c4555b1923c" alt=""><figcaption></figcaption></figure>

**6. (Optional) Add Secrets**

* Under **Step 4: Secrets**, you'll find the **Environment Variables** section. This is where you can securely store sensitive information like API keys and tokens.

  * Enter a **Key** (e.g., `API_KEY`) and a **Value** (e.g., `sk-abcdefgh`).

  <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2F432qUZTNQTsLn9kx5odj%2FScreenshot%202025-05-28%20at%2010.17.33%E2%80%AFAM.png?alt=media&amp;token=3e8657d4-309c-4ef7-bb32-421d6060cd31" alt=""><figcaption></figcaption></figure>
* Secrets Management information can be found [here](/overview/building-apps-in-health-universe/deploying-your-app-to-health-universe/secret-management).

**6. Configure Visibility**

* Under **Step 5: Privacy & Access**, choose whether your app should be:
  * **Private**: only you or workspace members can access it)
  * **Public**: anyone with the link can use it.

    <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FEQMBIee6vca0KS3dobVd%2FScreenshot%202025-05-28%20at%2010.19.48%E2%80%AFAM.png?alt=media&amp;token=dd5601a5-8057-4452-8284-828e75d8e1a5" alt=""><figcaption></figcaption></figure>

**7. Submit the Deployment**

* Click the **Deploy App** button at the bottom of the form.

  <div align="left"><figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2F4lfBjthRgwb1CjyddDHu%2FScreenshot%202025-05-28%20at%2010.21.10%E2%80%AFAM.png?alt=media&amp;token=a21608ff-49af-42fd-9958-9ad5ca40f06d" alt="" width="164"><figcaption></figcaption></figure></div>
* Your app will begin building. You’ll be shown real-time logs of the deployment process.

  <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2F3dYWamdtkSK6uccAMZwW%2FScreenshot%202025-05-28%20at%2010.22.47%E2%80%AFAM.png?alt=media&amp;token=97a54887-6ce1-437b-adfc-99a0247a1704" alt=""><figcaption></figcaption></figure>

**8. Watch the Logs**

* Monitor the build logs to ensure everything is running smoothly.
* Once deployment is complete, you'll be **automatically redirected** to your new app’s live page.
* Information on testing your app can be found [here](/overview/testing-your-app).


# Secret Management

If you have API keys or database passwords that you cannot store in your public GitHub repository, you should create a secret and access it in your code using the following approach. For example, if you want to access a private database, or use an LLM like ChatGPT, you'll need to have API keys to manage access.

**1)** First click **Deployments** on the top right of the navigation bar.

<figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FjIhdZzwJdzsfBlYEThGT%2FScreenshot%202025-05-28%20at%2010.37.25%E2%80%AFAM.png?alt=media&amp;token=48295e92-54ac-4491-8101-db8efed0db2c" alt=""><figcaption></figcaption></figure>

**2)** Click the **production box**

<figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FyHL6aulObdkKGEjG6H22%2FScreenshot%202025-05-28%20at%2010.41.11%E2%80%AFAM.png?alt=media&amp;token=b676b36c-d6f0-4e95-9595-1f971ab3e605" alt=""><figcaption></figcaption></figure>

**3)** Click the **secrets dropdown box**

<figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FicVTmFuv6AmrXSXnYa75%2FScreenshot%202025-05-28%20at%2010.42.20%E2%80%AFAM.png?alt=media&amp;token=94d7c326-c045-4ea3-814e-c762b320080d" alt=""><figcaption></figcaption></figure>

**4)** Click the **secrets dropdown box**

* Here you can create a secret using this form
* Important: Secrets can contain letters, numbers or the "-" and "\_" characters only. They must start with a letter or number. Access your secret using UPPERCASE from your code.

  ```
  import os
  foo = os.environ.get("FOO")
  ```
* Note, when you access a secret using os.environ.get() you access the secret using UPPERCASE variable names. If you do not use uppercase, you will not be able to access your secret.

<figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2F2aryB6PduNryUYxzfkcT%2Fimage.png?alt=media&amp;token=c733dc87-3997-4824-bf94-762bde9578f0" alt=""><figcaption></figcaption></figure>


# Connecting to an LLM

When integrating a Large Language Model (LLM) into your application, whether using **Streamlit** or **FastAPI**, it's important to securely manage the connection credentials (such as an API key) and ensure smooth communication with the model. Below are the steps to connect to an LLM in a general manner.

#### 1. S**et up your LLM API Key** Additional Notes:

To securely store and access your LLM API key, use the [**Secrets Management**](/overview/building-apps-in-health-universe/deploying-your-app-to-health-universe/secret-management).

For example:

```python
import os

LLM_API_KEY = os.environ.get("LLM_API_KEY")
```

#### 2. **Connect to the LLM from your code**:

Once you have the API key, you can establish a connection to the LLM. The steps for connecting to the model will depend on your specific LLM provider, but here's a general example of how you might set it up:

* **Install the required library:** If your LLM provider requires a specific Python library, ensure it’s installed.
* For example: `pip install some-llm-library`
* Set up the connection in your Streamlit or FastAPI app:

```python
import some_llm_library
import os

# Set up the API key
some_llm_library.api_key = os.environ.get("LLM_API_KEY")

# Example function to call the LLM
def query_llm(prompt):
    response = some_llm_library.Completion.create(
        model="your-model-name",  # Adjust model as needed
        prompt=prompt,
        max_tokens=150
    )
    return response["choices"][0]["text"].strip()
```

* Integrate with Streamlit or FastAPI:

In Streamlit, you could use the `query_llm()` function to process user input in real-time:

```python
import streamlit as st

st.title("LLM Query Example")

user_input = st.text_input("Ask something to the model:")
if user_input:
    response = query_llm(user_input)
    st.write(response)
```

In FastAPI, you could expose an endpoint for querying the LLM:

```python
from fastapi import FastAPI

app = FastAPI()

@app.get("/query_llm")
async def query_llm_endpoint(prompt: str):
    response = query_llm(prompt)
    return {"response": response}
```

#### Additional Notes:

* **Security Considerations**: Always retrieve your API keys securely using the Secrets Management process. Never hardcode your API keys directly into your codebase.
* **Rate Limits and Costs**: Be mindful of the rate limits and associated costs of using the LLM service. Ensure that your application handles errors and retries gracefully.
* **Error Handling**: Implement error handling for cases like invalid API keys, rate limits being exceeded, or network issues.

```python
try:
    response = query_llm(user_input)
except some_llm_library.LLMError as e:
    st.error(f"Error connecting to the model: {e}")
```

By following these steps, you can securely integrate an LLM into your Streamlit or FastAPI application, ensuring both security and functionality.


# Connecting to an external data source

When integrating an external data source (such as a database, file system, etc.) into your application, whether using Streamlit or FastAPI, it’s important to manage your credentials securely and establish a reliable connection. Below are the steps to connect to an external data source in a secure and efficient manner.

#### **1. Set up Credentials for the External Data Source**:

To securely store and access credentials (such as API keys, database passwords, or other sensitive information), use the [**Secrets Management**](/overview/building-apps-in-health-universe/deploying-your-app-to-health-universe/secret-management) feature on the Health Universe website.

**Important**: Secrets can only contain letters, numbers, or the `-` and `_` characters. They must start with a letter or number. For example, `DB_PASSWORD`, `API_KEY`, or `DATA_SOURCE_CREDENTIALS`.

#### **2. Access your secret in your code:**

Once you've created the secret, you can retrieve it securely using `os.environ.get()` in your Python code. Make sure to use uppercase for the secret name when accessing it:

```python
import os

DB_PASSWORD = os.environ.get("DB_PASSWORD")
API_KEY = os.environ.get("API_KEY")
```

#### **3. Connect to the External Data Source from your Code:**

Once you have your credentials, you can establish a connection to the external data source. The process for connecting depends on the type of data source. Below are examples for connecting to a database and an API.

* **Connecting to a Database:** To connect to a database (e.g., PostgreSQL, MySQL, etc.), you can use a library like `psycopg2` or `sqlalchemy`.
* For PostgreSQL: `pip install psycopg2`

```python
import psycopg2
import os

# Retrieve credentials from secrets management
DB_HOST = os.environ.get("DB_HOST")
DB_NAME = os.environ.get("DB_NAME")
DB_USER = os.environ.get("DB_USER")
DB_PASSWORD = os.environ.get("DB_PASSWORD")

# Connect to the database
connection = psycopg2.connect(
    host=DB_HOST,
    dbname=DB_NAME,
    user=DB_USER,
    password=DB_PASSWORD
)

# Example function to query the database
def query_database(query):
    with connection.cursor() as cursor:
        cursor.execute(query)
        result = cursor.fetchall()
    return result
```

* **Connecting to an External API:** To connect to an external API, you can use the `requests` library.
* For requests: `pip install requests`

```python
import requests
import os

# Retrieve credentials from secrets management
API_KEY = os.environ.get("API_KEY")

# Example function to call the external API
def query_api(endpoint, params=None):
    headers = {
        "Authorization": f"Bearer {API_KEY}"
    }
    response = requests.get(endpoint, headers=headers, params=params)
    if response.status_code == 200:
        return response.json()
    else:
        return {"error": "Failed to fetch data"}
```

#### **4. Use the External Data Source in Your Streamlit or FastAPI App:**

Once the connection to the external data source is established, you can easily use it within your Streamlit or FastAPI app.

* **Streamlit Example:**

```python
import streamlit as st

st.title("External Data Source Example")

query = st.text_input("Enter SQL query to execute:")
if query:
    result = query_database(query)
    st.write(result)
```

* **FastAPI Example:**

```python
from fastapi import FastAPI

app = FastAPI()

@app.get("/query_database")
async def query_database_endpoint(query: str):
    result = query_database(query)
    return {"result": result}

@app.get("/query_api")
async def query_api_endpoint(endpoint: str, params: dict = None):
    result = query_api(endpoint, params)
    return {"result": result}
```

#### Additional Notes:

* **Security Considerations**: Always retrieve your credentials securely using the Secrets Management process. Never hardcode sensitive data directly into your codebase.
* **Connection Pooling**: For databases, consider using connection pooling to optimize performance and resource usage. Libraries like `SQLAlchemy` or `psycopg2` support pooling.
* **Error Handling**: Always include error handling when working with external data sources, as network issues, incorrect credentials, or unavailable resources may cause failures.

```python
try:
    result = query_database(query)
except Exception as e:
    st.error(f"Error querying the database: {e}")
```

By following these steps, you can securely and efficiently connect to an external data source from your Streamlit or FastAPI application.


# Testing your app

After deploying or redeploying your app, follow these steps to test it and confirm everything is working as expected:

#### 1. Test Your Application

**For Streamlit apps:**

1. Go to your app on [Health Universe](https://www.healthuniverse.com/).
2. Click the **Launch App** button to open it in a new tab.
3. Interact with the app to confirm it’s running correctly and that your updates appear as expected.
4. You should also fill in the description information on the apps page so that others can understand the app and know how best to use it.

**For FastAPI apps:**

1. FastAPI apps do not launch directly from the app page — they are launched within Navigator.
   1. Launch Navigator
   2. Search for app (in search bar)
   3. Click "Use Tool"
   4. Run app.

#### 2. Review Deployment Logs (Optional but Recommended)

If something doesn’t look right, or just to be thorough:

1. In the **left-hand menu**, click **Deployments**.
2. Locate the **Production** deployment box.
3. Click **Build Logs** to review details of the most recent deployment.

> 🛠️ Use the logs to troubleshoot issues such as build errors, missing dependencies, or runtime exceptions.


# Re-deploying your app

#### Deploy vs Redeploy

The first time that you create an app, you should deploy the app within Health Universe. Health Universe creates a secure container for the app, configures that virtual environment, compiles the code, and does a number of configurations to make sure that they app runs within the Navigator or Health Universe environment.

Once you've successfully deployed an app, you should use re-deploy. This allows you to update and change the code, but the rest of the app container and configuration remains the same. Redeployment is the most efficient (and effective) way to incorporate changes in your code. If you deploy the same app multiple times, you'll get unstable behaviors in Navigator, and difficultly in finding (and using) the apps that you've deployed.

#### 1. **Push Changes to GitHub**

Before redeploying, make sure all your updates are committed and pushed to your app's GitHub repository:

```bash
git add .
git commit -m "Your update message"
git push origin main
```

> 🔁 Ensure you're pushing to the branch connected to your Health Universe app (usually `main`).

#### 2. Redeploy on Health Universe

Once your changes are live on GitHub:

* **Go to your app** on [Health Universe](https://www.healthuniverse.com/).
* Click **Deployments** on the top right of the navigation bar.

  <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FjIhdZzwJdzsfBlYEThGT%2FScreenshot%202025-05-28%20at%2010.37.25%E2%80%AFAM.png?alt=media&amp;token=48295e92-54ac-4491-8101-db8efed0db2c" alt=""><figcaption></figcaption></figure>
* Find and click the production deployment box.

  <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FyHL6aulObdkKGEjG6H22%2FScreenshot%202025-05-28%20at%2010.41.11%E2%80%AFAM.png?alt=media&amp;token=b676b36c-d6f0-4e95-9595-1f971ab3e605" alt=""><figcaption></figcaption></figure>
* Click the **Redeploy Button**.

  <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FOEkZdso84wfxCBd9iKbU%2FScreenshot%202025-05-28%20at%2010.46.50%E2%80%AFAM.png?alt=media&amp;token=1bb1a36f-fdcf-4655-aad9-423d6691e25f" alt=""><figcaption></figcaption></figure>

#### 3. Review Deployment Logs

* After triggering a redeploy, **click the Build Logs** box to open it.
* Review the **logs** to confirm the app is rebuilding and redeploying successfully.

#### 4. Launch Your Updated App

* **Refresh the page** after deployment completes.
* Click **Launch** to open and verify your changes are live.


# Document your app

Once you've deployed your app to Health Universe, it's time to **Document** it. This step helps ensure your app is discoverable, well-understood, and appropriately used.

Here's how to document your app effectively:

#### 1. **Go to Your App Page**

Navigate to your app’s page on [healthuniverse.com](https://healthuniverse.com/).

#### 2. **Enter Edit Mode**

Click **Edit** in the top-right corner of your app page to begin editing.

#### 3. **Update Core Information**

You can now modify:

* **App Name**
* **Description**
* **Research Area**

  <div align="left"><figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FB2cGKYqX8CS0mEhCNzDo%2FScreenshot%202025-05-28%20at%2010.49.07%E2%80%AFAM.png?alt=media&amp;token=56d53789-2acf-47d0-a31d-053870db9423" alt="" width="375"><figcaption></figcaption></figure></div>

Make sure these are clear, concise, and descriptive to help Navigator accurately surface your app in the right clinical or administrative contexts.

#### 4. **Add Documentation**

In addition to the basics, you’ll have the option to add detailed paragraphs for each of the following sections:

* 🩺 **Use Case**:
  * Describe the specific clinical or administrative scenario your app supports. Be specific — this helps Navigator understand when to surface your app.
* 📚 **Evidence**:
  * Add any relevant data, clinical studies, validation results, or references that support the app’s effectiveness or accuracy.
* ⚠️ **Limitations**:
  * Be transparent about what the app **cannot** do or situations where caution should be used.
* 💡 **Owner’s Insigh**t:

  * Share the developer’s or clinician’s unique perspective. Why did you build this app? How do you envision it being used? This adds credibility and context.

  <div align="left"><figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FufDC02C1eDNJOcBARO6I%2FScreenshot%202025-05-28%20at%2010.50.06%E2%80%AFAM.png?alt=media&amp;token=6da3310b-96ec-4c30-8e7a-158d3a07dd19" alt="" width="375"><figcaption></figcaption></figure></div>

#### 5. Modify App Settings

* **Hide GitHub Repository**
  * Toggle **Yes/No** to choose whether to hide your GitHub repo from the app page.
* **Privacy Setting**
  * Set your app to **Private** (only you can access it) or **Public** (anyone with the link can use it).
* **Delete Metadata**
  * If needed, you can delete your app’s metadata from this section.

<figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FMXDUIR7lhXr4BT3KZDjq%2FScreenshot%202025-05-28%20at%2010.50.40%E2%80%AFAM.png?alt=media&amp;token=f575f8b4-16ff-493f-b732-8cd91260bac8" alt=""><figcaption></figcaption></figure>

#### 6. Submit & Preview

* Click **Save Changes** to save any edits you made
* Refresh the page
* Go to the **Overview** tab
* Scroll down to view your updates live

  <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FlVcHjl4AgGYsDKnNaWZq%2Fezgif-83aa9c7878286e.gif?alt=media&amp;token=6440fd2c-aabf-465c-a0a6-6e72bf72fa4e" alt=""><figcaption></figcaption></figure>

Documenting your app thoroughly not only helps others use it properly — it also improves its visibility and utility within the platform.


# Deleting your App on Health Universe

Follow these steps to fully delete your app and its deployments from Health Universe.

### 1. Delete the Production Deployment

* Go to your app on [Health Universe](https://www.healthuniverse.com).
* Click **Deployments** on the top right of the navigation bar.

  <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FjIhdZzwJdzsfBlYEThGT%2FScreenshot%202025-05-28%20at%2010.37.25%E2%80%AFAM.png?alt=media&amp;token=48295e92-54ac-4491-8101-db8efed0db2c" alt=""><figcaption></figcaption></figure>
* Locate the **Production** deployment box and click on it.

  <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FyHL6aulObdkKGEjG6H22%2FScreenshot%202025-05-28%20at%2010.41.11%E2%80%AFAM.png?alt=media&amp;token=b676b36c-d6f0-4e95-9595-1f971ab3e605" alt=""><figcaption></figcaption></figure>
* Scroll down to locate and click the red **Delete Deployment Button**.

  <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FYQzqRLGMZXYbQmCh9Lp5%2FScreenshot%202025-05-28%20at%2011.04.16%E2%80%AFAM.png?alt=media&amp;token=120f4b78-2f35-455b-8b6e-dc3e17a3c9e3" alt=""><figcaption></figcaption></figure>
* A confirmation warning will appear — read the message and click **Yes** to confirm.

  <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FeNQzkpEvimKWhWvTjwpZ%2FScreenshot%202025-05-28%20at%2011.05.03%E2%80%AFAM.png?alt=media&amp;token=1d50b65c-29a7-4ce3-bedd-815225e36cca" alt=""><figcaption></figcaption></figure>

> ✅ You should see a popup in the bottom right corner confirming the app has been deleted. The production box will also disappear (you may need to refresh the page).

### 2. Delete the App Itself

* After the deployment is deleted, click **Edit App** in the top-right corner of the page to enter **edit mode**.
* Find and click the **red Delete App** button.

  <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FEDx4TKcbykOcpMOsWSya%2FScreenshot%202025-05-28%20at%2011.06.22%E2%80%AFAM.png?alt=media&amp;token=09a11c19-46a7-4c18-af5f-fc594bfb4db2" alt=""><figcaption></figcaption></figure>
* A final confirmation warning will appear — read it and click **Delete App** to confirm.

  <figure><img src="https://1951440936-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FS80fbh8wWG71Qtm77IKh%2Fuploads%2FitThUSLy2JQvq75m2GU5%2FScreenshot%202025-05-28%20at%2011.06.46%E2%80%AFAM.png?alt=media&amp;token=0ed709cd-05f6-4ab1-bc13-461fb76dec7e" alt=""><figcaption></figcaption></figure>

> 🚨 Once confirmed, your app will be **permanently deleted**, and you will be redirected to your workspace.


# Overview

The Observer is Health Universe's built-in monitoring suite for tracking AI agent activity in production. It combines an SDK integration layer with a real-time dashboard, giving you visibility into safety metrics, usage patterns, costs, latency, and user feedback.

* [Dashboard](/overview/observability/dashboard) — monitor agent activity with customizable widgets
* [SDK Integration](/overview/observability/sdk-integration) — connect your agents to the Observer


# Observer Dashboard

The Observer dashboard provides real-time visibility into how Navigator and your A2A agents are performing in production — including safety metrics, usage patterns, costs, latency, and user feedback.

## Accessing the Dashboard

The Observer is available to **workspace admins** from the workspace sidebar:

**Workspace view:** `/workspaces/<your-org>/observer`

> The Observer requires admin permissions for your workspace. If you don't see it in the sidebar, contact your workspace owner.

## Overview

The dashboard is built around **widgets** — individual metric panels that you can add, remove, and rearrange. Each widget pulls data from your production traces and refreshes automatically.

When you first open the Observer, the system can suggest relevant widgets based on the data available in your workspace. You can accept the suggestions, browse the full widget catalog, or build your dashboard manually.

### Time Period

All dashboard widgets respect a global time period selector. Choose from:

* **7 days** — recent activity
* **30 days** — monthly trends
* **90 days** — quarterly view

## Widget Categories

The Observer provides 27+ widget types organized into categories:

### Usage

Track how your agents are being used:

* **Trace Count** — total number of agent executions
* **Unique Users** — distinct users interacting with agents
* **Unique Sessions** — distinct conversation threads
* **Trace Activity** — chart of daily agent executions over time
* **Daily Active Users** — DAU trend
* **Top Users** — most active users by trace count
* **Top Trace Names** — most frequently executed agent operations

### Cost

Monitor LLM API spending:

* **Total Cost** — aggregate LLM cost for the period
* **Cost Trend** — cost over time chart
* **Model Cost Breakdown** — spending split by model (e.g. GPT-4o, Claude, etc.)

### Performance

Understand latency and throughput:

* **Average Latency** — mean execution time across traces
* **Latency Trend** — latency over time
* **Tokens per Trace** — average token consumption
* **Output Speed** — tokens per second

### Safety

Monitor AI safety scores:

* **Score Card** — displays a safety metric (e.g. hallucination, toxicity) with average value, evaluation count, and 7-day trend
* **Score Trend** — safety score over time
* **Incident Counter** — traces flagged with high safety scores
* **Flagged Traces Table** — detailed view of problematic traces for investigation

> Safety scores use an inverted scale where **lower values are better** (e.g. a low hallucination score means fewer hallucinations). The exception is user feedback, where higher values are positive.

### Quality

* **Score Distribution** — histogram showing how scores are distributed across traces

### Benchmarks

* **Benchmark Comparison** — Navigator's accuracy on medical benchmark datasets compared to baseline models, with exact-match and LLM-jury evaluation methods

### Workspace (Admin)

Workspace-level widgets for administrators:

* **App Count** — total deployed applications
* **App Executions** — usage across all apps
* **App Health** — status overview of all deployed apps
* **Top Apps** — most-used applications
* **Member Engagement** — workspace member activity

## Customizing Your Dashboard

### Adding Widgets

1. Click the **catalog** button to browse all available widget types
2. Widgets are organized by category — click a category to filter
3. Click **Add** on any widget to add it to your dashboard
4. Use **Add All** to add every widget in a category at once

### AI-Suggested Widgets

When setting up a new dashboard, click **Suggestions** to have the system analyze your available data and recommend 6–12 relevant widgets. This is the fastest way to get started.

### Removing Widgets

Click the remove button on any widget to remove it from your dashboard. Your dashboard configuration is saved automatically.

### Resetting to Defaults

Use the reset option to clear your dashboard and start fresh with suggested widgets.

## User Feedback

The Observer tracks thumbs-up and thumbs-down feedback from end users interacting with Navigator. This feedback appears in:

* **User Feedback Feed** — chronological list of feedback events
* **Score Card** widgets configured for the `user_feedback` metric

User feedback uses a **direct scale** (1 = positive, 0 = negative), unlike safety scores which are inverted.


# SDK Integration

The Observer aggregates production traces from your deployed agents into a single monitoring [dashboard](/overview/observability/dashboard). This page covers how trace data flows into the Observer.

## Data Sources

The Observer collects traces from two sources:

* **Navigator** — the platform's AI orchestration engine. Navigator traces are collected automatically with no configuration required.
* **A2A Agents** — any agent you deploy using the Health Universe A2A SDK. Traces are captured automatically when the agent runs in production on Health Universe.

Traces are filtered to **production data only** and scoped to your workspace. Data is cached and refreshes at intervals ranging from 2 minutes to 1 hour depending on the widget type.

## What Gets Traced

The SDK automatically captures:

* **Agent execution** — every `process_message()` call with duration, metadata, input/output
* **LLM calls** — model name, token usage, latency (via OpenTelemetry instrumentation)
* **Document operations** — list, download, write operations as child spans
* **Inter-agent communication** — calls to other agents via `call_agent()`

For full details on trace structure and adding custom instrumentation, see the [A2A Observability guide](/overview/building-apps-in-health-universe/developing-your-health-universe-app/working-with-a2a-agents/observability).


# Overview

The Health Universe CLI (`hu`) lets you manage applications, deployments, workspaces, and projects directly from your terminal. Everything you can do in the web dashboard — deploying apps, managing collaborators, switching workspaces — you can do from the command line.

## Prerequisites

* **Node.js 18** or later
* **npm** 9 or later (included with Node.js)
* A [Health Universe account](https://www.healthuniverse.com)

## Installation

```bash
npm install -g @health-universe/cli
```

After installation, the `hu` command is available globally.

To **upgrade** to the latest version:

```bash
npm install -g @health-universe/cli@latest
```

To **uninstall**:

```bash
npm uninstall -g @health-universe/cli
```

> Uninstalling removes the CLI but leaves your config and credentials in `~/.hu/`. To remove those as well, delete the directory: `rm -rf ~/.hu`

## Quick Start

```bash
# 1. Log in (opens your browser)
hu login

# 2. Check your identity and active workspace
hu whoami

# 3. List your apps
hu apps list

# 4. Deploy an app and wait for it to finish
hu apps deploy <app-id> --wait

# 5. Check deployment status
hu apps status <app-id>
```

The CLI authenticates through your browser — `hu login` opens a sign-in page and no passwords are entered in the terminal. Tokens are valid for **24 hours** and refresh automatically. Credentials are stored in `~/.hu/credentials`.

All read commands support `--json` for machine-readable output:

```bash
hu apps list --json | jq '.[].name'
```

## Usage Modes

The CLI works in two modes:

* **Single-command mode** — run any command directly from your shell. Great for scripts and automation.

  ```bash
  hu apps list --json | jq '.[].name'
  ```
* **Interactive REPL mode** — run `hu` with no arguments to enter an interactive session. The REPL displays your authentication status and active workspace, and supports tab completion for all commands.

  ```
  $ hu

    ╦ ╦╔═╗╔═╗╦ ╔╦╗╦ ╦  ╦ ╦╔╗╔╦╦  ╦╔═╗╦═╗╔═╗╔═╗
    ╠═╣║╣ ╠═╣║  ║ ╠═╣  ║ ║║║║║╚╗╔╝║╣ ╠╦╝╚═╗║╣
    ╩ ╩╚═╝╩ ╩╩═╝╩ ╩ ╩  ╚═╝╝╚╝╩ ╚╝ ╚═╝╩╚═╚═╝╚═╝

    Version:    0.2.0
    Status:     AUTHENTICATED
    Workspace:  my-org

  hu> apps list
  hu> apps deploy abc123 --wait
  ```

  REPL-only commands:

  | Command         | Description                 |
  | --------------- | --------------------------- |
  | `help`          | Show all available commands |
  | `clear`         | Clear the screen            |
  | `exit` / `quit` | Exit the REPL               |

## What's Next

* [Command Reference](/overview/command-line-interface/command-reference) — full list of all available commands
* [Troubleshooting](/overview/command-line-interface/troubleshooting) — common errors and how to fix them


# Command Reference

Complete reference for all `hu` CLI commands.

***

## Authentication

### `hu login`

Open your browser to authenticate with Health Universe.

```bash
hu login
hu login --api-url http://localhost:3002 --auth-url http://localhost:3000
```

| Option       | Description                                                    |
| ------------ | -------------------------------------------------------------- |
| `--api-url`  | Override the API endpoint for this login                       |
| `--auth-url` | Override the URL of the browser sign-in page (the NextJS host) |

### `hu logout`

Clear the stored authentication token.

```bash
hu logout
```

### `hu whoami`

Display the currently authenticated user and active workspace.

```bash
hu whoami
hu whoami --json
```

***

## Apps

Manage applications in the current workspace.

### `hu apps list`

List all applications in the active workspace.

```bash
hu apps list
hu apps list --json
```

### `hu apps create`

Create a new application.

```bash
hu apps create --name "My App" --repo owner/repo --sdk FA --main-file main.py
```

| Option          | Description                                                  |
| --------------- | ------------------------------------------------------------ |
| `--name`        | Application name                                             |
| `--repo`        | GitHub repository in `owner/repo` format                     |
| `--sdk`         | App runtime: `FA` (FastAPI), `A2A` (Agent), `SL` (Streamlit) |
| `--category`    | Research area category                                       |
| `--description` | App description                                              |
| `--main-file`   | Entry point file (e.g. `main.py`)                            |
| `--private`     | Make the app private                                         |
| `--json`        | Output as JSON                                               |

### `hu apps delete`

Delete an application.

```bash
hu apps delete <app-id>
```

### `hu apps open`

Open an application in your browser.

```bash
hu apps open <app-id>
```

### `hu apps status`

Check the deployment status of an application.

```bash
hu apps status <app-id>
hu apps status <app-id> --json
```

### `hu apps deploy`

Trigger a deployment for an application.

```bash
hu apps deploy <app-id>
hu apps deploy <app-id> --wait     # Poll until deployment completes
hu apps deploy <app-id> --json
```

| Option   | Description                                                           |
| -------- | --------------------------------------------------------------------- |
| `--wait` | Poll every 5 seconds until the deployment finishes (up to 10 minutes) |
| `--json` | Output as JSON                                                        |

> With `--wait`, the CLI polls the deployment status every 5 seconds for up to **10 minutes**. If the deployment hasn't completed by then, the command exits — the deployment itself continues on the server. Check progress with `hu apps status <app-id>`.

### `hu apps logs`

View logs for an application deployment.

```bash
hu apps logs <app-id>              # Deployment/runtime logs
hu apps logs <app-id> --build      # Build logs
hu apps logs <app-id> --json
```

| Option    | Description                             |
| --------- | --------------------------------------- |
| `--build` | Show build logs instead of runtime logs |
| `--json`  | Output as JSON                          |

### `hu apps health`

Trigger a health check for an application.

```bash
hu apps health <app-id>
hu apps health <app-id> --json
```

### `hu apps archive`

Archive an application. This shuts down the running instance to save resources while keeping all configuration, metadata, and deployment history intact. The app will no longer be accessible to users until it is unarchived.

```bash
hu apps archive <app-id>
```

### `hu apps unarchive`

Restore an archived application and bring it back online.

```bash
hu apps unarchive <app-id>
```

***

## App Collaborators

### `hu apps collaborators list`

List collaborators for an application.

```bash
hu apps collaborators list <app-id>
hu apps collaborators list <app-id> --json
```

### `hu apps collaborators add`

Add collaborators to an application by email. Pass multiple emails as a comma-separated string.

```bash
hu apps collaborators add <app-id> "user1@example.com,user2@example.com"
```

> If some emails don't match existing Health Universe accounts, those will be skipped and the CLI will tell you which ones weren't found. The remaining valid emails are still added.

### `hu apps collaborators remove`

Remove a collaborator from an application.

```bash
hu apps collaborators remove <app-id> <collaborator-id>
```

***

## App Domains

Setting up a custom domain is a multi-step process:

1. **Add** the domain to your app
2. **Configure DNS** — add the CNAME or A record shown in the output to your DNS provider
3. **Verify** — confirm that DNS is configured correctly
4. **Activate** — switch live traffic to the custom domain

### `hu apps domains list`

List custom domains for an application.

```bash
hu apps domains list <app-id>
hu apps domains list <app-id> --json
```

### `hu apps domains add`

Add a custom domain to an application. The output will include DNS records you need to configure with your domain provider before verification.

```bash
hu apps domains add <app-id> app.example.com
```

### `hu apps domains verify`

Verify that DNS records are configured correctly for the domain. Run this after you've added the required DNS records at your domain provider.

```bash
hu apps domains verify <app-id> <domain-id>
hu apps domains verify <app-id> <domain-id> --json
```

### `hu apps domains activate`

Activate a verified custom domain so it starts serving traffic.

```bash
hu apps domains activate <app-id> <domain-id>
```

### `hu apps domains remove`

Remove a custom domain from an application.

```bash
hu apps domains remove <app-id> <domain-id>
```

***

## Orgs

The `orgs` command group manages your active workspace (organization). Workspaces are identified by their **slug** — a short, human-readable identifier shown everywhere in CLI output (banner, `whoami`, `orgs list`, `config show`).

### `hu orgs list`

List all workspaces you have access to.

```bash
hu orgs list
hu orgs list --json
```

### `hu orgs switch`

Switch the active workspace. All subsequent commands will operate in the selected workspace.

```bash
hu orgs switch <slug>          # Preferred — switch by slug
hu orgs switch <workspace-id>  # ID fallback
hu orgs switch personal        # Switch to your personal workspace
```

### `hu orgs members`

List members of the active workspace.

```bash
hu orgs members
hu orgs members --json
```

***

## Projects

### `hu projects list`

List projects in the active workspace.

```bash
hu projects list
hu projects list --json
```

### `hu projects create`

Create a new project in the active workspace.

```bash
hu projects create "My Project"
hu projects create "My Project" --json
```

### `hu projects delete`

Delete a project.

```bash
hu projects delete <project-id>
```

### `hu projects collaborators list`

List collaborators on a project.

```bash
hu projects collaborators list <project-id>
```

### `hu projects collaborators add`

Add a collaborator to a project.

```bash
hu projects collaborators add <project-id> <user-id> --role admin
```

| Option   | Description                         |
| -------- | ----------------------------------- |
| `--role` | Role to assign: `admin` or `member` |

### `hu projects collaborators remove`

Remove a collaborator from a project.

```bash
hu projects collaborators remove <project-id> <user-id>
```

***

## Threads

### `hu threads list`

List threads in the active workspace.

```bash
hu threads list
hu threads list --search "diabetes"
hu threads list --project <project-id> --limit 10
hu threads list --json
```

| Option      | Description                   |
| ----------- | ----------------------------- |
| `--search`  | Filter threads by search term |
| `--project` | Filter by project ID          |
| `--limit`   | Maximum number of results     |
| `--json`    | Output as JSON                |

### `hu threads delete`

Delete a thread.

```bash
hu threads delete <thread-id>
```

***

## Config

### `hu config show`

Display the current CLI configuration, including token status.

```bash
hu config show
hu config show --json
```

### `hu config set`

Update a configuration value.

```bash
hu config set apiUrl https://apps.healthuniverse.com
hu config set authUrl https://www.healthuniverse.com
```

| Key       | Description                                       | Default                           |
| --------- | ------------------------------------------------- | --------------------------------- |
| `apiUrl`  | NestJS API base URL                               | `https://apps.healthuniverse.com` |
| `authUrl` | URL of the browser sign-in page (the NextJS host) | `https://www.healthuniverse.com`  |

### `hu config unset`

Clear a single configuration key.

```bash
hu config unset apiUrl
hu config unset orgId    # Also clears orgSlug — they're paired
```

### `hu config reset`

Wipe the entire configuration (and credentials by default). Useful for starting clean without removing `~/.hu/` manually.

```bash
hu config reset                 # Clears config + token
hu config reset --keep-token    # Clears config but preserves the auth token
```

| Option         | Description                                                             |
| -------------- | ----------------------------------------------------------------------- |
| `--keep-token` | Preserve the stored auth token; only reset URLs and workspace selection |

***

## Admin

> These commands are only available to platform administrators.

### `hu admin stats`

Show platform-wide statistics (threads, users) across 24h, 7d, 30d, and all-time.

```bash
hu admin stats
hu admin stats --json
```

### `hu admin users`

List recently active users.

```bash
hu admin users
hu admin users --limit 20 --json
```

### `hu admin orgs`

List all workspaces on the platform.

```bash
hu admin orgs
hu admin orgs --json
```

***

## Common Workflows

### Deploy an app and monitor it

```bash
hu apps deploy <app-id> --wait
hu apps logs <app-id>
hu apps status <app-id>
```

### Set up a new app from scratch

```bash
hu login
hu orgs switch my-org
hu apps create --name "Patient Risk Model" --repo myorg/risk-model --sdk FA --main-file main.py
hu apps deploy <app-id> --wait
hu apps open <app-id>
```

### Add collaborators to an app

```bash
hu apps collaborators add <app-id> "alice@example.com,bob@example.com"
hu apps collaborators list <app-id>
```

### Set up a custom domain

```bash
hu apps domains add <app-id> risk-model.example.com
# Configure DNS records as instructed, then:
hu apps domains verify <app-id> <domain-id>
hu apps domains activate <app-id> <domain-id>
```

### Switch between environments

```bash
# Use local dev environment
hu config set apiUrl http://localhost:3002
hu config set authUrl http://localhost:3000
hu login

# Switch back to production
hu config set apiUrl https://apps.healthuniverse.com
hu config set authUrl https://www.healthuniverse.com
hu login
```


# Troubleshooting

Common issues and how to resolve them.

***

## "Not logged in. Run 'hu login' to authenticate."

Most CLI commands require authentication. Run `hu login` to open a browser window and sign in with your Health Universe account.

If you've already logged in and still see this error, your credentials file may be missing or corrupted. Run `hu login` again to generate a fresh token.

***

## "Session expired. Run 'hu login' to re-authenticate."

Authentication tokens are valid for 24 hours. After that, you need to log in again:

```bash
hu login
```

You can check your current authentication status at any time with:

```bash
hu config show    # Shows token status: valid, expired, or not set
hu whoami         # Shows your user info if authenticated
```

***

## Login times out or browser doesn't open

The `hu login` command opens a browser window and waits up to **2 minutes** for you to complete sign-in. If it times out:

1. Run `hu login` again
2. If the browser doesn't open automatically, the CLI prints a URL you can copy and paste into your browser manually

If you're on a headless server or remote machine without a browser, you'll need to log in from a machine with a browser and copy the credentials file (`~/.hu/credentials`) to the remote machine.

***

## "Failed to fetch..." or generic API errors

These errors usually mean the CLI couldn't reach the Health Universe API. Check the following:

1. **Are you connected to the internet?**
2. **Is the API URL correct?** Run `hu config show` to verify. The default production URL is `https://apps.healthuniverse.com`.
3. **Are you logged in?** Run `hu whoami` to check.
4. **Is the server up?** Try opening `https://www.healthuniverse.com` in your browser.

If you previously changed the API URL for local development and forgot to switch back:

```bash
hu config set apiUrl https://apps.healthuniverse.com
hu config set authUrl https://www.healthuniverse.com
```

***

## Workspace-related errors

Some commands require an active workspace to be selected. If you see unexpected results or empty lists:

1. Check which workspace you're in: `hu whoami`
2. List available workspaces: `hu orgs list`
3. Switch to the correct workspace: `hu orgs switch my-org`

> `hu projects list` requires an active workspace. `hu apps list` defaults to your personal workspace if none is selected.

***

## Deploy with `--wait` exits before deployment finishes

The `--wait` flag polls for up to **10 minutes**. If your deployment takes longer (common for large applications or first-time builds), the CLI exits but the deployment continues on the server.

To check progress after the CLI exits:

```bash
hu apps status <app-id>
hu apps logs <app-id>
hu apps logs <app-id> --build    # If still building
```

***

## Resetting the CLI

To start completely fresh, use the built-in reset command:

```bash
hu config reset           # Clears config + token
hu config reset --keep-token   # Clears URLs/workspace but preserves the token
hu login                       # Re-authenticate if needed
```

This clears your stored token, configuration, and workspace selection.

You can also nuke the entire local data directory directly:

```bash
rm -rf ~/.hu
hu login
```


# Data Formats, Standards & Privacy

Handling data correctly is crucial when deploying healthcare applications on the Health Universe platform. This page provides guidelines on managing data formats, standards, and privacy when working with healthcare data.

Health Universe uses the USCDI standards from the Office of the National Coordinator for Health IT (ONC), the Consolidated CDA standard and FHIR standards. While stand-alone apps that only get their input from the user won't require these standards, any app that wants to interact with EHR data should use these standards for the best performance.

### Data Formats and Standards

To ensure interoperability and consistency among applications and data sources, it is essential to use standard data formats and structures. Some common healthcare data formats and standards include:

1. **HL7 (Health Level Seven)**: A set of international standards for exchanging, integrating, sharing, and retrieving electronic health information.
2. **FHIR (Fast Healthcare Interoperability Resources)**: A modern standard for exchanging healthcare information electronically, designed to be easy to implement and integrate into healthcare applications.
3. **DICOM (Digital Imaging and Communications in Medicine)**: A standard for storing, transmitting, and sharing medical images and related information.
4. **CSV (Comma-Separated Values)** and **JSON (JavaScript Object Notation)**: Widely-used, human-readable data formats that can represent structured data like patient records, lab results, or device data.

When working with healthcare data, always consider the data format and standards that best suit your application and the systems you will be interacting with.

### Data Privacy and Security

Healthcare data often includes sensitive information, so it is essential to ensure data privacy and security:

1. **Data encryption**: Use industry-standard encryption algorithms to encrypt data at rest and in transit, protecting sensitive information from unauthorized access.
2. **Access control**: Implement authentication and authorization mechanisms to restrict access to sensitive data based on user roles and permissions.
3. **Data anonymization**: Remove or obfuscate personally identifiable information (PII) from datasets to protect patient privacy while preserving the utility of the data for analysis and modeling.
4. **Data retention**: Establish policies for data retention and deletion, ensuring that sensitive data is only stored for as long as necessary and disposed of securely.

### Compliance with Regulations

Healthcare applications must comply with applicable data protection and privacy regulations, such as:

1. **HIPAA (Health Insurance Portability and Accountability Act)**: A US law that governs the handling of protected health information (PHI) by healthcare providers, insurers, and their business associates.
2. **GDPR (General Data Protection Regulation)**: A European regulation that sets strict rules on the processing and protection of personal data, including health data, for individuals within the European Union.
3. **Other local and regional regulations**: Depending on your application's target market and users, you may need to comply with additional data protection and privacy regulations specific to certain countries or regions.

Ensure that your healthcare application adheres to all relevant data protection and privacy regulations, implementing the necessary safeguards and procedures to maintain compliance.

By following these guidelines on data formats, standards, privacy, and security, you can develop and deploy healthcare applications on the Health Universe platform that are interoperable, reliable, and compliant with industry regulations, ultimately contributing to improved patient care and outcomes.


# External Tools and Libraries

The Health Universe platform is designed to be flexible and extensible, supporting a wide range of data science libraries to enhance the development and deployment of health applications. This page provides an overview of popular tools and libraries that you may find helpful during the development of your application.

### Data Science and Machine Learning Libraries

1. **NumPy**: A fundamental library for numerical computing in Python, NumPy provides support for large, multi-dimensional arrays and matrices, along with a collection of mathematical functions.
   * Website: <https://numpy.org/>
2. **Pandas**: A powerful library for data manipulation and analysis, Pandas provides data structures like DataFrames and Series, along with functions for data cleaning, aggregation, and transformation.
   * Website: <https://pandas.pydata.org/>
3. **Scikit-learn**: A comprehensive library for machine learning and data mining, Scikit-learn offers tools for classification, regression, clustering, dimensionality reduction, and more.
   * Website: <https://scikit-learn.org/>
4. **TensorFlow**: An open-source machine learning framework developed by Google, TensorFlow is widely used for developing, training, and deploying deep learning models.
   * Website: <https://www.tensorflow.org/>
5. **Keras**: A high-level neural network API built on top of TensorFlow, Keras provides a user-friendly interface for designing, training, and evaluating deep learning models.
   * Website: <https://keras.io/>
6. **PyTorch**: An open-source machine learning framework developed by Facebook, PyTorch is known for its dynamic computational graph and ease of use, making it popular for research and development.
   * Website: <https://pytorch.org/>

### UI & Data Visualization Libraries

1. **Streamlit**: An open-source Python library for creating custom web applications for machine learning and data science projects with minimal coding.
   * Website: <https://streamlit.io/>
2. **Matplotlib**: A widely used plotting library for Python, Matplotlib provides tools for creating static, animated, and interactive visualizations in various formats.
   * Website: <https://matplotlib.org/>
3. **Seaborn**: A statistical data visualization library based on Matplotlib, Seaborn offers a high-level interface for drawing attractive and informative statistical graphics.
   * Website: <https://seaborn.pydata.org/>
4. **Plotly**: A graphing library for creating interactive, web-based visualizations, Plotly supports a wide range of chart types, including scatter plots, line charts, bar charts, and more.
   * Website: <https://plotly.com/python/>

By leveraging these external tools and libraries, you can streamline the development process, enhance the capabilities of your healthcare applications, and ensure seamless integration with the Health Universe platform. Explore these resources and consider incorporating them into your projects to improve efficiency and effectiveness.


